Контрактне тестування API
1) Навіщо контрактне тестування
Контракт фіксує очікування клієнта і обіцянки провайдера: маршрути/методи, заголовки, схеми тіл, статуси, семантика помилок і обмеження. Мета - ловити несумісні зміни до інтеграції і випускати версії безпечно, без важких E2E.
2) Підходи
Consumer-Driven Contracts (CDC): клієнт формує очікування (Pact і аналоги); провайдер регулярно їх верифікує.
Специфікаційний: контракт як єдине джерело істини (OpenAPI/Protobuf/GraphQL SDL); тести валідують реалізацію проти специфікації.
Подієвий: схеми повідомлень (Avro/JSON Schema/Protobuf) + правила сумісності в брокері/реєстрі.
3) HTTP/REST: специфікаційний потік
1. Контракт: OpenAPI 3. x (схеми, приклади, коди).
2. Лінт і статаналіз: стиль, обов'язкові поля, єдині коди.
3. Валідація реалізації: генератор тестів проти OpenAPI (schemathesis/Dredd-підхід) + ручні негативи.
4. Снапшоти: фіксуйте приклади відповідей, ETag семантику, заголовки ідемпотентності.
yaml paths:
/v1/payments:
post:
responses:
"201": { $ref: "#/components/responses/PaymentCreated" }
"409": { description: Duplicate by Idempotency-Key }
"422": { description: Schema/Business validation failed }
4) CDC (Pact-підхід)
Життєвий цикл:1. Consumer пише тест, формує pact-файл (очікування).
2. Публікація в брокер (артефакт).
3. Provider в CI піднімає сервіс (або контракт-стійку), верифікує pact'и.
4. Брокер обчислює can-i-deploy матрицю сумісності.
Приклад consumer-тесту (псевдо-JS):js pact
.given("wallet exists")
.uponReceiving("get wallet")
.withRequest({ method:"GET", path:"/v1/wallets/w123", headers:{ "Authorization": term({generate:"Bearer x", matcher:/^Bearer\s.+/}) }})
.willRespondWith({
status: 200,
headers: { "Content-Type": "application/json" },
body: like({ id:"w123", currency: "EUR", balance: 0 })
});
5) gRPC/Protobuf контракти
Контракт -'.proto'з версіонуванням пакетів/служб.
Сумісність: не перевикористувати теги, тільки додавати нові з optional, не видаляти використовувані поля; резервувати номери.
Тести: генерація стаба сервера/клієнта, прокат автозгенеренних кейсів + негативи (unknown fields, size-limits).
6) Подієві контракти (Kafka/NATS/...)
Схеми: Avro/JSON Schema/Protobuf в Schema Registry.
Політика сумісності: 'BACKWARD'( часто достатньо) або'FULL'.
Тести продьюсера: валідує повідомлення проти схеми; тести консьюмера: приймає стару і нову версію.
Інваріанти: ключі ідемпотентності, порядок/повторюваність, семантика дедуплікації.
json
{"type":"record","name":"PayoutCreated","fields":[
{"name":"payoutId","type":"string"},
{"name":"amount","type":"double"},
{"name":"currency","type":{"type":"string","logicalType":"iso-4217"}}
]}
7) Сумісність і версії
Backward-compatible (рекомендований мінімум): додавайте необов'язкові поля, не ламайте існуючі.
Forward-compatible: споживачі ігнорують невідомі поля.
Full: і те, і інше.
Версіонування: `path (/v1)`, `Accept: application/vnd. brand. v2+json`, `proto package v2`.
Deprecation-політика: вікно виводу (наприклад, 90 днів), що попереджають заголовки/події.
8) Негативні і помилки - це теж контракт
Стандартизуйте коди: 400/401/403/404/409/422/429/5xx, обов'язкові поля'code','message','trace _ id'.
Розміри/ліміти - частина контракту (413/414/431).
Ідемпотентність: поведінка при повторі (409 vs 201 same id).
Заголовки: `Retry-After` при 429/503, `Idempotency-Key`, `Content-Language` и т. п.
json
{ "code":"validation_error", "message":"amount must be ≥ 1", "trace_id":"..." }
9) Даними управляє контракт
Приклади/примірочні відповіді (examples) - живі, валідуються в CI.
Фікстури для CDC - мінімальні, детерміновані.
Генерація даних - property-based для чисел/дат; але не ламати стабільність снапшотів.
10) Пайплайн в CI/CD (референс)
1. Lint/validate: OpenAPI/Proto/Avro (`validate`, style-линер).
2. Publish: контракт як артефакт (брокер/реєстр).
3. Verify: провайдер проганяє CDC-пакети/специфікаційні тести.
4. can-i-deploy gate: без зеленої матриці реліз заборонений.
5. Diff: перевірка, що зміни сумісні (semantic diff).
6. Report: JUnit/HTML, список порушень/дефініцій.
yaml jobs:
lint:...
publish-contract: needs: [lint]
verify-provider: needs: [publish-contract]
can-i-deploy:
if: always()
steps: [run: pact-broker can-i-deploy...]
11) Інструменти (за класами задач)
Лінт/валідатори: openapi-linters, protobuf-lint, avro-tools.
CDC: Pact-сімейство/брокер, Spring Cloud Contract, Hoverfly (HTTP записи/реплеї).
Специфікаційні раннери: schemathesis/Dredd-подібний підхід, Postman test + JSON Schema.
Diff: semantic-diff OpenAPI/Proto/Avro (виявляє breaking-зміни).
Контейнери: Testcontainers для підняття провайдера/стійки контрактів.
12) Антипатерни
«Спец-док» окремо від коду → розсинхронізація. Зберігайте контракт поруч з сервісом.
Моки стороннього сервісу замість контракту → крихкість при апдейтах.
Випадкові відповіді/генерація без прив'язки до схеми → флейки.
Зміна типів/обов'язковості полів без версії.
Silent-розширення без повідомлень/депрекації.
Відсутність негативних контрактів і кодів помилок.
13) Специфіка iGaming/фінансів
Формалізуйте грошові поля: «amount» - decimal з масштабом, валюта - ISO-4217, інваріанти сум.
Контракти платежів/вебхуків: HMAC/mTLS, anti-replay ('X-Timestamp'вікно), ідемпотентність,'Retry-After'.
Регіональність/тенанти: обов'язкові заголовки'X-Tenant/X-Region', локалізація повідомлень.
Події: незмінні журнали (audit), ключі дедуплікації, гарантії доставки (at least once + ідемпотентні хендлери).
14) Приклади «скелетів» тестів
14. 1 Schemathesis-стиль (псевдо)
bash schemathesis run openapi. yaml --checks all --hypothesis-deadline=200
14. 2 Postman як специфікаційний раннер
js pm. test ("Scheme/v1/wallets/{ id} is valid," () => {
const schema = pm. collectionVariables. get("wallet_get_schema");
pm. expect(ajv. validate(JSON. parse(schema), pm. response. json())). to. be. true;
});
14. 3 Provider-верифікація CDC (псевдо)
bash pact-broker can-i-deploy --pacticipant wallets --version $SHA --to-environment staging
15) Чек-лист prod-готовності
- Контракти в репозиторії, CI валідує і публікує артефакти.
- CDC включений для критичних інтеграцій; працює broker/matrix «can-i-deploy».
- Політика сумісності (HTTP/gRPC/Events) задокументована; автоматичний semantic-diff.
- Негативні контракти: помилки, ліміти розмірів, ідемпотентність,'Retry-After'.
- Тест-дані детерміновані; снапшоти прикладів підтримуються.
- Версіонування та deprecation: терміни, повідомлення, заголовки.
- Для подій - Schema Registry і режим сумісності; продьюсер/консьюмер тестують обидві версії.
- Артефакти: JUnit/HTML, звіти diff/verify, матриця сумісності.
- Інцидент-процедура: швидкий rollback контракту/фіча-прапор, повідомлення інтеграторам.
16) TL; DR
Фіксуйте очікування в контрактах і проганяйте їх автоматично: CDC для клієнтських очікувань, специфікаційні тести для відповідності реалізації, реєстр схем для подій. Тримайте сувору політику сумісності і негативні контракти (помилки, ліміти, ідемпотентність). Не можна релізувати без зеленої can-i-deploy матриці і semantic-diff без breaking-змін.