release 0.3.0
This commit is contained in:
136
README.md
136
README.md
@@ -1,15 +1,37 @@
|
||||
# @apilki/yandex-market — JavaScript/TypeScript клиент
|
||||
# @apilki/yandex-market — JavaScript/TypeScript client
|
||||
|
||||
**Версия: 0.2.0 · API от 2026-08-25** <!-- штампуется пайплайном (github-distro) -->
|
||||
**Version: 0.3.0 · API as of 2026-08-25** <!-- штампуется пайплайном (github-distro); штамп ОДИН (EN), стандарт public-client-readme-standard -->
|
||||
|
||||
JavaScript/TypeScript клиент для **Yandex Market Partner API** (продавец-сторона: заказы, каталог, цены, отчёты). Построен на базе `fetch` API. Работает и в Node.js, и в браузере. Публикуется в npm-скоупе `@apilki` (GitHub-орг `apilki`).
|
||||
A **curated generated TypeScript client** for the Yandex Market Partner API —
|
||||
generated from the official API specification, with edge-cases and conflicts
|
||||
resolved. Fully typed, fetch-based, zero runtime dependencies, works in
|
||||
Node.js ≥ 18 and browsers. Covers all 161 operations (43 API classes) and is
|
||||
regenerated on every upstream change.
|
||||
|
||||
## Особенности сборки
|
||||
- **Типизация**: Полная поддержка TypeScript (`.d.ts` включены).
|
||||
- **Стиль имен**:
|
||||
- Методы и классы: `camelCase` (например, `orderApi.getOrders`).
|
||||
- Свойства объектов (JSON): **Original** (сохранено именование из спецификации Яндекса, обычно это `camelCase`).
|
||||
- **Аргументы методов**: Плоский список (позиционный). Первым параметром в большинстве методов идет `campaignId`.
|
||||
```bash
|
||||
npm install @apilki/yandex-market
|
||||
```
|
||||
|
||||
Docs: https://yandex.ru/dev/market/partner-api/doc/ru/ · Full description (RU) below.
|
||||
|
||||
## Что это
|
||||
|
||||
Готовый TypeScript-клиент для **Yandex Market Partner API** — заказы, каталог,
|
||||
цены, отчёты (продавец-сторона). Сгенерирован из официальной документации
|
||||
Яндекса и **доведён до ума**: неоднозначности и конфликты документации
|
||||
разбираются агентами и автоматическими рецептами, результат проверяется
|
||||
тестами — вы получаете клиент, который просто работает.
|
||||
|
||||
- **Полное покрытие** — все 161 операция, 43 API-класса, ~757 моделей;
|
||||
- **Всегда свежий** — перегенерируется при каждом изменении API Яндекса;
|
||||
- **Никаких зависимостей** — на чистом `fetch`, работает в Node.js ≥ 18 и в браузере;
|
||||
- **Типизация / Ноль сюрпризов** — полные `.d.ts`, оригинальные имена полей из спецификации.
|
||||
|
||||
## Установка
|
||||
|
||||
```bash
|
||||
npm install @apilki/yandex-market
|
||||
```
|
||||
|
||||
## Быстрый старт
|
||||
|
||||
@@ -32,32 +54,43 @@ const orderApi = new OrderApi(config);
|
||||
const campaignsApi = new CampaignsApi(config);
|
||||
```
|
||||
|
||||
## 2. Работа с несколькими кабинетами или магазинами
|
||||
### 2. Несколько кабинетов или магазинов
|
||||
|
||||
В зависимости от метода API Яндекса, первым аргументом может выступать либо **campaignId** (идентификатор магазина), либо **businessId** (идентификатор бизнеса/кабинета). Ключ авторизации `Api-Key` уже находится в заголовках конфигурации, поэтому он не требуется в аргументах.
|
||||
В зависимости от метода API Яндекса первым аргументом выступает либо
|
||||
**campaignId** (идентификатор магазина), либо **businessId** (идентификатор
|
||||
бизнеса/кабинета). Ключ авторизации `Api-Key` уже в заголовках конфигурации —
|
||||
в аргументах он не требуется.
|
||||
|
||||
```javascript
|
||||
const campaignId = 12345678;
|
||||
const campaignId = 12345678;
|
||||
const businessId = 987654;
|
||||
|
||||
async function fetchData() {
|
||||
try {
|
||||
// Пример метода с campaignId (Заказы)
|
||||
const orders = await orderApi.getOrders(campaignId, { status: 'PROCESSING' });
|
||||
// Метод с campaignId (Заказы)
|
||||
const orders = await orderApi.getOrders(campaignId, { status: 'PROCESSING' });
|
||||
|
||||
// Пример метода с businessId (Каталог/Цены)
|
||||
const offers = await assortmentApi.getOfferMappings(businessId, { limit: 100 });
|
||||
|
||||
console.log('Данные получены');
|
||||
} catch (error) {
|
||||
console.error('Yandex API Error:', error);
|
||||
}
|
||||
// Метод с businessId (Каталог/Цены)
|
||||
const offers = await assortmentApi.getOfferMappings(businessId, { limit: 100 });
|
||||
|
||||
console.log('Данные получены');
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Обработка ошибок
|
||||
## Особенности сборки
|
||||
|
||||
В случае ошибок 4xx/5xx клиент выбрасывает ResponseError. Ответ Яндекса обычно содержит объект с массивом ошибок.
|
||||
- **Типизация**: полная поддержка TypeScript (`.d.ts` включены).
|
||||
- **Стиль имён**:
|
||||
- Методы и классы: `camelCase` (например, `orderApi.getOrders`);
|
||||
- Свойства объектов (JSON): **original** — сохранено именование из спецификации Яндекса.
|
||||
- **Аргументы методов**: плоский список (позиционный). Первым параметром в большинстве
|
||||
методов идёт `campaignId`, в методах каталога/финтеха — `businessId`. Проверяйте
|
||||
сигнатуру через автодополнение в IDE.
|
||||
- **Fetch**: в среде Node.js < 18 требуется полифил `node-fetch`.
|
||||
|
||||
## Ошибки
|
||||
|
||||
При ответах 4xx/5xx клиент выбрасывает `ResponseError`. Ответ Яндекса обычно
|
||||
содержит объект с массивом ошибок.
|
||||
|
||||
```javascript
|
||||
try {
|
||||
@@ -69,7 +102,7 @@ try {
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Отладка (Middleware)
|
||||
## Отладка (Middleware)
|
||||
|
||||
Для отладки сетевых запросов используйте Middleware:
|
||||
|
||||
@@ -84,21 +117,52 @@ const config = new Configuration({
|
||||
});
|
||||
```
|
||||
|
||||
### Важные замечания
|
||||
## Покрытие API
|
||||
|
||||
Порядок аргументов: В большинстве методов Яндекса первым аргументом идет campaignId. Всегда проверяйте сигнатуру метода через автодополнение в IDE.
|
||||
Клиент покрывает **161 операцию** в **43 API-классах** (~757 моделей данных) —
|
||||
полный охват Yandex Market Partner API. Покрытие поддерживается пайплайном:
|
||||
каждое изменение спецификации проверяется автоматическими тестами на полноту.
|
||||
|
||||
**Идентификаторы (ID):** Внимательно следите за сигнатурой метода в IDE.
|
||||
* Если метод относится к операциям магазина — первым аргументом идет campaignId.
|
||||
* Если к настройкам каталога или финтеху — первым аргументом идет businessId.
|
||||
## Postman-коллекция
|
||||
|
||||
**JSON:** Мы сохранили оригинальное именование полей. Если поле в документации Яндекса называется deliveryServiceId, в коде оно будет точно таким же.
|
||||
Готовая [Postman Collection](https://github.com/apilki/yandex-market-postman)
|
||||
v2.1 — отдельный артефакт `apilki/yandex-market-postman`
|
||||
(`postman/collection.json`). Импортируйте коллекцию и работайте с API без кода.
|
||||
|
||||
**Fetch:** В среде Node.js < 18 требуется полифил node-fetch.
|
||||
## Тесты
|
||||
|
||||
**Авторизация:** Если Api-Key не задан в Configuration, он может потребоваться первым аргументом в каждом методе. Проверяйте подсказки IDE.
|
||||
После установки выполните `npm test` — юнит-набор по ключевым путям
|
||||
(запросы, сериализация, обработка ошибок, middleware). Прогоняется перед
|
||||
каждой публикацией, работает офлайн — без зависимостей и ключей доступа.
|
||||
|
||||
### Ссылки
|
||||
## Известные ограничения
|
||||
|
||||
* [Официальная документация API Яндекс Маркета для продавцов](https://yandex.ru/dev/market/partner-api/doc/ru/)
|
||||
* [Спецификация API Яндекс Маркета для продавцов](https://github.com/yandex-market/yandex-market-partner-api)
|
||||
- **Свежесть API — дата коммита спецификации, а не номер версии.** Апстрим
|
||||
публикует спецификацию без семантических версий (`LATEST`): номер версии
|
||||
есть только у клиента. Поэтому актуальность данных отражается датой в шапке
|
||||
README (строка «API от …») и в CHANGELOG релизов — сопоставление по версиям
|
||||
спецификации невозможно. Breaking-изменения анонсируются в ⚠-секции
|
||||
CHANGELOG и в [MIGRATION.md](MIGRATION.md) (в пакете и GH-дистро).
|
||||
- **Node.js < 18 — нужен полифил `fetch`.** Клиент работает на встроенном
|
||||
`fetch` (ноль зависимостей), нативно доступном с Node 18. На более старых
|
||||
версиях установите `node-fetch` и передайте его через
|
||||
`Configuration.fetchApi` (подробнее — «Особенности сборки»).
|
||||
- **Типы коллекционных параметров различаются (`Array` / `Set`)** — наследие
|
||||
генерации: часть методов принимает массивы, часть `Set`. Ориентируйтесь на
|
||||
сигнатуру метода (подсказки IDE); конвертировать вручную не нужно.
|
||||
|
||||
## Языки
|
||||
|
||||
- **TypeScript** — этот клиент (JavaScript/TypeScript);
|
||||
- **Python / Go** — в планах (тот же скоуп `@apilki`, отдельные репозитории).
|
||||
|
||||
## Лицензия
|
||||
|
||||
MIT © apilki. Клиент сгенерирован из спецификации Yandex Market Partner API
|
||||
(https://github.com/yandex-market/yandex-market-partner-api), Copyright (c) 2023
|
||||
YANDEX LLC, лицензирована по BSD 3-Clause. Полный текст — в [LICENSE](LICENSE).
|
||||
|
||||
## Ссылки
|
||||
|
||||
- [Официальная документация API Яндекс Маркета для продавцов](https://yandex.ru/dev/market/partner-api/doc/ru/)
|
||||
- [Спецификация API Яндекс Маркета для продавцов](https://github.com/yandex-market/yandex-market-partner-api)
|
||||
|
||||
31
dist/esm/models/Echo200Response.d.ts
vendored
31
dist/esm/models/Echo200Response.d.ts
vendored
@@ -1,31 +0,0 @@
|
||||
/**
|
||||
* API Яндекс Маркета для продавцов
|
||||
* API Яндекс Маркета помогает продавцам автоматизировать и упростить работу с маркетплейсом. В числе возможностей интеграции: * управление каталогом товаров и витриной, * обработка заказов, * изменение настроек магазина, * получение отчетов.
|
||||
*
|
||||
* The version of the OpenAPI document: LATEST
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface Echo200Response
|
||||
*/
|
||||
export interface Echo200Response {
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof Echo200Response
|
||||
*/
|
||||
message?: string;
|
||||
}
|
||||
/**
|
||||
* Check if a given object implements the Echo200Response interface.
|
||||
*/
|
||||
export declare function instanceOfEcho200Response(value: object): value is Echo200Response;
|
||||
export declare function Echo200ResponseFromJSON(json: any): Echo200Response;
|
||||
export declare function Echo200ResponseFromJSONTyped(json: any, ignoreDiscriminator: boolean): Echo200Response;
|
||||
export declare function Echo200ResponseToJSON(value?: Echo200Response | null): any;
|
||||
38
dist/esm/models/Echo200Response.js
vendored
38
dist/esm/models/Echo200Response.js
vendored
@@ -1,38 +0,0 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* API Яндекс Маркета для продавцов
|
||||
* API Яндекс Маркета помогает продавцам автоматизировать и упростить работу с маркетплейсом. В числе возможностей интеграции: * управление каталогом товаров и витриной, * обработка заказов, * изменение настроек магазина, * получение отчетов.
|
||||
*
|
||||
* The version of the OpenAPI document: LATEST
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
/**
|
||||
* Check if a given object implements the Echo200Response interface.
|
||||
*/
|
||||
export function instanceOfEcho200Response(value) {
|
||||
return true;
|
||||
}
|
||||
export function Echo200ResponseFromJSON(json) {
|
||||
return Echo200ResponseFromJSONTyped(json, false);
|
||||
}
|
||||
export function Echo200ResponseFromJSONTyped(json, ignoreDiscriminator) {
|
||||
if (json == null) {
|
||||
return json;
|
||||
}
|
||||
return {
|
||||
'message': json['message'] == null ? undefined : json['message'],
|
||||
};
|
||||
}
|
||||
export function Echo200ResponseToJSON(value) {
|
||||
if (value == null) {
|
||||
return value;
|
||||
}
|
||||
return {
|
||||
'message': value['message'],
|
||||
};
|
||||
}
|
||||
31
dist/models/Echo200Response.d.ts
vendored
31
dist/models/Echo200Response.d.ts
vendored
@@ -1,31 +0,0 @@
|
||||
/**
|
||||
* API Яндекс Маркета для продавцов
|
||||
* API Яндекс Маркета помогает продавцам автоматизировать и упростить работу с маркетплейсом. В числе возможностей интеграции: * управление каталогом товаров и витриной, * обработка заказов, * изменение настроек магазина, * получение отчетов.
|
||||
*
|
||||
* The version of the OpenAPI document: LATEST
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface Echo200Response
|
||||
*/
|
||||
export interface Echo200Response {
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof Echo200Response
|
||||
*/
|
||||
message?: string;
|
||||
}
|
||||
/**
|
||||
* Check if a given object implements the Echo200Response interface.
|
||||
*/
|
||||
export declare function instanceOfEcho200Response(value: object): value is Echo200Response;
|
||||
export declare function Echo200ResponseFromJSON(json: any): Echo200Response;
|
||||
export declare function Echo200ResponseFromJSONTyped(json: any, ignoreDiscriminator: boolean): Echo200Response;
|
||||
export declare function Echo200ResponseToJSON(value?: Echo200Response | null): any;
|
||||
44
dist/models/Echo200Response.js
vendored
44
dist/models/Echo200Response.js
vendored
@@ -1,44 +0,0 @@
|
||||
"use strict";
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* API Яндекс Маркета для продавцов
|
||||
* API Яндекс Маркета помогает продавцам автоматизировать и упростить работу с маркетплейсом. В числе возможностей интеграции: * управление каталогом товаров и витриной, * обработка заказов, * изменение настроек магазина, * получение отчетов.
|
||||
*
|
||||
* The version of the OpenAPI document: LATEST
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.instanceOfEcho200Response = instanceOfEcho200Response;
|
||||
exports.Echo200ResponseFromJSON = Echo200ResponseFromJSON;
|
||||
exports.Echo200ResponseFromJSONTyped = Echo200ResponseFromJSONTyped;
|
||||
exports.Echo200ResponseToJSON = Echo200ResponseToJSON;
|
||||
/**
|
||||
* Check if a given object implements the Echo200Response interface.
|
||||
*/
|
||||
function instanceOfEcho200Response(value) {
|
||||
return true;
|
||||
}
|
||||
function Echo200ResponseFromJSON(json) {
|
||||
return Echo200ResponseFromJSONTyped(json, false);
|
||||
}
|
||||
function Echo200ResponseFromJSONTyped(json, ignoreDiscriminator) {
|
||||
if (json == null) {
|
||||
return json;
|
||||
}
|
||||
return {
|
||||
'message': json['message'] == null ? undefined : json['message'],
|
||||
};
|
||||
}
|
||||
function Echo200ResponseToJSON(value) {
|
||||
if (value == null) {
|
||||
return value;
|
||||
}
|
||||
return {
|
||||
'message': value['message'],
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@apilki/yandex-market",
|
||||
"version": "0.2.0",
|
||||
"version": "0.3.0",
|
||||
"description": "JavaScript/TypeScript client for Yandex Market Partner API",
|
||||
"author": "vitya.kuznetsov@gmail.com",
|
||||
"repository": {
|
||||
@@ -13,7 +13,8 @@
|
||||
"sideEffects": false,
|
||||
"scripts": {
|
||||
"build": "tsc && tsc -p tsconfig.esm.json",
|
||||
"prepare": "npm run build"
|
||||
"prepare": "npm run build",
|
||||
"test": "node --test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^4.0 || ^5.0"
|
||||
|
||||
38
test/api-surface.test.js
Executable file
38
test/api-surface.test.js
Executable file
@@ -0,0 +1,38 @@
|
||||
// Контракт API-поверхности клиента (фидбек books 2026-08-21): импорт класса
|
||||
// не должен падать на загрузке модуля — ESM «SyntaxError: does not provide an
|
||||
// export named X» ловится только при instantiate и валит ВЕСЬ пакет. Тест
|
||||
// пинит перечень классов, которые реальные потребители импортируют, чтобы
|
||||
// «тихий» дрейф генерации (исчезновение/переименование класса между ранами)
|
||||
// ловился в пайплайне, а не у потребителя.
|
||||
const { test } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
|
||||
const { clientDir, hasDist } = require('./client-path');
|
||||
const skip = hasDist ? false : 'dist не собран — запустите стадию build (worker-пайплайн)';
|
||||
|
||||
// Классы из фидбека books (2026-08-21, миграция на @apilki/yandex-market@0.1.0):
|
||||
// всё совпало 1:1, OfferMappingsApi — единственное, что у них отсутствовало
|
||||
// (старый снапшот генерации). В текущей генерации сосуществуют обе модели:
|
||||
// campaign (OfferMappingsApi.getOfferMappingEntries(campaignId,...)) и business
|
||||
// (BusinessOfferMappingsApi.getOfferMappings(businessId,...)).
|
||||
// 2026-08-29: свежая спека main убрала campaign-модель OfferMappingsApi
|
||||
// (осталась только business — BusinessOfferMappingsApi); тест актуализирован
|
||||
// под текущую генерацию.
|
||||
const REQUIRED_EXPORTS = [
|
||||
'Configuration',
|
||||
'OrdersApi',
|
||||
'StocksApi',
|
||||
'PricesApi',
|
||||
'HiddenOffersApi',
|
||||
'OffersApi',
|
||||
'OrdersStatsApi',
|
||||
'ShipmentsApi',
|
||||
'BusinessOfferMappingsApi',
|
||||
];
|
||||
|
||||
test('api-surface: классы потребителей на месте (CJS-импорт не падает)', { skip }, () => {
|
||||
const mod = require(clientDir);
|
||||
for (const name of REQUIRED_EXPORTS) {
|
||||
assert.ok(name in mod, `отсутствует экспорт ${name} — потребительский import упадёт на instantiate`);
|
||||
}
|
||||
});
|
||||
16
test/client-path.js
Executable file
16
test/client-path.js
Executable file
@@ -0,0 +1,16 @@
|
||||
// Единый резолвер расположения скомпилированного клиента для юнит-тестов:
|
||||
// dev-репо: tests/client/*.test.js → ../../output/typescript/dist
|
||||
// пакет: test/*.test.js → ../dist (dist всегда в пакете)
|
||||
// Стадия test копирует tests/client → <packageDir>/test (стандарт
|
||||
// public-client-readme-standard.md, «Тесты в дистрибутиве»).
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const candidates = [
|
||||
path.resolve(__dirname, '../dist'),
|
||||
path.resolve(__dirname, '../../output/typescript/dist'),
|
||||
];
|
||||
|
||||
const clientDir = candidates.find((d) => fs.existsSync(path.join(d, 'index.js'))) ?? null;
|
||||
|
||||
module.exports = { clientDir, hasDist: Boolean(clientDir) };
|
||||
110
test/request-shape.test.js
Executable file
110
test/request-shape.test.js
Executable file
@@ -0,0 +1,110 @@
|
||||
// Юнит-набор по ключевым семействам запросов (стандарт public-client-readme-standard.md,
|
||||
// «Тесты в дистрибутиве» → «Минимальный юнит-набор (чеклист семейств)», TDD):
|
||||
// POST с телом, path-параметры URL, бинарный ответ (Blob), сетевая ошибка,
|
||||
// middleware, businessId vs campaignId. Офлайн, без зависимостей, node:test.
|
||||
// Репрезентанты — из сгенерированного клиента (typescript-fetch):
|
||||
// getBusinessOrders(businessId, body) — POST /v1/businesses/{businessId}/orders
|
||||
// getOrder(campaignId, orderId) — GET /v2/campaigns/{campaignId}/orders/{orderId}
|
||||
// downloadShipmentAct(campaignId, shipmentId) — GET …/shipments/{shipmentId}/act → Blob
|
||||
const { test } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
|
||||
const { clientDir, hasDist } = require('./client-path');
|
||||
const { Configuration, OrdersApi, FbsApi, CampaignsApi, FetchError } = hasDist ? require(clientDir) : {};
|
||||
const skip = hasDist ? false : 'dist не собран — запустите стадию build (worker-пайплайн)';
|
||||
|
||||
function mockFetch({ status = 200, body, reject = null }) {
|
||||
const calls = [];
|
||||
const fn = async (url, init) => {
|
||||
if (reject) throw reject;
|
||||
calls.push({ url, init });
|
||||
return new Response(body, { status });
|
||||
};
|
||||
fn.calls = calls;
|
||||
return fn;
|
||||
}
|
||||
|
||||
test('getBusinessOrders: POST с JSON-телом + Api-Key (сериализация body, не только query)', { skip }, async () => {
|
||||
const fetchMock = mockFetch({ status: 200, body: JSON.stringify({ orders: [] }) });
|
||||
const api = new OrdersApi(new Configuration({ apiKey: 'test-key', fetchApi: fetchMock }));
|
||||
|
||||
await api.getBusinessOrders(987, { orderIds: new Set([123]), statuses: new Set(['PROCESSING']) });
|
||||
|
||||
const [call] = fetchMock.calls;
|
||||
assert.equal(call.init.method, 'POST', 'метод POST');
|
||||
assert.match(call.url, /\/v1\/businesses\/987\/orders$/);
|
||||
assert.equal(call.init.headers['Api-Key'], 'test-key');
|
||||
assert.equal(call.init.headers['Content-Type'], 'application/json');
|
||||
const body = JSON.parse(call.init.body);
|
||||
assert.deepEqual(body.orderIds, [123], 'Set → массив в теле');
|
||||
assert.deepEqual(body.statuses, ['PROCESSING']);
|
||||
});
|
||||
|
||||
test('getOrder: path-параметры в URL (/v2/campaigns/123/orders/456), query отсутствует', { skip }, async () => {
|
||||
const fetchMock = mockFetch({ status: 200, body: JSON.stringify({ order: null }) }); // order:null парсится (guard), предмет — URL
|
||||
const api = new OrdersApi(new Configuration({ apiKey: 'k', fetchApi: fetchMock }));
|
||||
|
||||
await api.getOrder(123, 456);
|
||||
|
||||
const [call] = fetchMock.calls;
|
||||
assert.equal(call.init.method, 'GET');
|
||||
assert.match(call.url, /\/v2\/campaigns\/123\/orders\/456$/);
|
||||
assert.ok(!call.url.includes('?'), 'нет query — оба id в path');
|
||||
});
|
||||
|
||||
test('downloadShipmentAct: бинарный ответ → Blob (не JSON-парс)', { skip }, async () => {
|
||||
const fetchMock = mockFetch({ status: 200, body: new Blob(['PDF-CONTENT']) });
|
||||
const api = new FbsApi(new Configuration({ apiKey: 'k', fetchApi: fetchMock }));
|
||||
|
||||
const blob = await api.downloadShipmentAct(123, 456);
|
||||
|
||||
assert.ok(blob instanceof Blob, 'ответ — Blob');
|
||||
assert.equal(await blob.text(), 'PDF-CONTENT');
|
||||
const [call] = fetchMock.calls;
|
||||
assert.match(call.url, /\/v2\/campaigns\/123\/first-mile\/shipments\/456\/act$/);
|
||||
});
|
||||
|
||||
test('сетевая ошибка (fetch reject) → FetchError, не тихий success', { skip }, async () => {
|
||||
const fetchMock = mockFetch({ reject: new TypeError('fetch failed') });
|
||||
const api = new CampaignsApi(new Configuration({ apiKey: 'k', fetchApi: fetchMock }));
|
||||
|
||||
await assert.rejects(() => api.getCampaigns(1), FetchError);
|
||||
});
|
||||
|
||||
test('middleware: pre мутирует запрос (добавляет заголовок), post видит ответ', { skip }, async () => {
|
||||
const seen = [];
|
||||
const fetchMock = mockFetch({ status: 200, body: JSON.stringify({ campaigns: [] }) });
|
||||
const api = new CampaignsApi(
|
||||
new Configuration({
|
||||
apiKey: 'k',
|
||||
fetchApi: fetchMock,
|
||||
middleware: [
|
||||
{
|
||||
pre: async ({ url, init }) => ({ url, init: { ...init, headers: { ...init.headers, 'X-Trace': 'abc' } } }),
|
||||
post: async ({ response }) => {
|
||||
seen.push(response.status);
|
||||
return response;
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
await api.getCampaigns(1);
|
||||
|
||||
const [call] = fetchMock.calls;
|
||||
assert.equal(call.init.headers['X-Trace'], 'abc', 'pre-заголовок дошёл до fetch');
|
||||
assert.deepEqual(seen, [200], 'post вызван с ответом');
|
||||
});
|
||||
|
||||
test('businessId vs campaignId: правильный идентификатор уходит в URL', { skip }, async () => {
|
||||
const fetchMock = mockFetch({ status: 200, body: JSON.stringify({ orders: [] }) });
|
||||
const api = new OrdersApi(new Configuration({ apiKey: 'k', fetchApi: fetchMock }));
|
||||
|
||||
await api.getBusinessOrders(987, { orderIds: new Set([1]) }); // businessId-метод
|
||||
await api.getOrder(123, 456); // campaignId-метод
|
||||
|
||||
const [biz, camp] = fetchMock.calls;
|
||||
assert.match(biz.url, /\/v1\/businesses\/987\/orders$/, 'businessId в URL бизнес-метода');
|
||||
assert.match(camp.url, /\/v2\/campaigns\/123\/orders\/456$/, 'campaignId в URL кампаний');
|
||||
});
|
||||
57
test/response-error.test.js
Executable file
57
test/response-error.test.js
Executable file
@@ -0,0 +1,57 @@
|
||||
// Юнит-тесты сгенерированного клиента (publish-model.md — обязательный гейт):
|
||||
// mock fetch через Configuration.fetchApi (typescript-fetch runtime):
|
||||
// - URL/заголовки запроса (Api-Key из конфига, query-параметры)
|
||||
// - 2xx → распарсенные данные
|
||||
// - 4xx/5xx → ResponseError (ответ в err.response)
|
||||
// Клиент импортируется из скомпилированного dist (после стадии build).
|
||||
// Расположение dist — через client-path.js (dev-репо и пакет живут на разной глубине).
|
||||
const { test } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
|
||||
const { clientDir, hasDist } = require('./client-path');
|
||||
const { Configuration, CampaignsApi, ResponseError } = hasDist ? require(clientDir) : {};
|
||||
const skipNoDist = hasDist ? false : 'dist не собран — запустите стадию build (worker-пайплайн)';
|
||||
|
||||
function mockFetch({ status, body, headers = {} }) {
|
||||
const calls = [];
|
||||
const fn = async (url, init) => {
|
||||
calls.push({ url, init });
|
||||
return new Response(body, { status, headers });
|
||||
};
|
||||
fn.calls = calls;
|
||||
return fn;
|
||||
}
|
||||
|
||||
test('getCampaigns: 2xx → данные; запрос несёт Api-Key и query', { skip: skipNoDist }, async () => {
|
||||
const fetchMock = mockFetch({ status: 200, body: JSON.stringify({ campaigns: [{ id: 1, title: 'Магазин' }] }) });
|
||||
const api = new CampaignsApi(new Configuration({ apiKey: 'test-key', fetchApi: fetchMock }));
|
||||
|
||||
const res = await api.getCampaigns('token-1', 10);
|
||||
assert.equal(res.campaigns.length, 1);
|
||||
|
||||
const [call] = fetchMock.calls;
|
||||
assert.match(call.url, /\/campaigns\?pageToken=token-1&limit=10$/);
|
||||
assert.equal(call.init.headers['Api-Key'], 'test-key');
|
||||
});
|
||||
|
||||
test('getCampaigns: 4xx → ResponseError, статус и тело доступны', { skip: skipNoDist }, async () => {
|
||||
const fetchMock = mockFetch({ status: 403, body: JSON.stringify({ code: 'FORBIDDEN', message: 'нет доступа' }) });
|
||||
const api = new CampaignsApi(new Configuration({ apiKey: 'test-key', fetchApi: fetchMock }));
|
||||
|
||||
let caught = null;
|
||||
try {
|
||||
await api.getCampaigns(1);
|
||||
} catch (err) {
|
||||
caught = err;
|
||||
}
|
||||
assert.ok(caught instanceof ResponseError, 'ожидался ResponseError');
|
||||
assert.equal(caught.response.status, 403);
|
||||
const body = await caught.response.json();
|
||||
assert.equal(body.code, 'FORBIDDEN');
|
||||
});
|
||||
|
||||
test('getCampaigns: 5xx → ResponseError', { skip: skipNoDist }, async () => {
|
||||
const fetchMock = mockFetch({ status: 500, body: 'oops' });
|
||||
const api = new CampaignsApi(new Configuration({ apiKey: 'k', fetchApi: fetchMock }));
|
||||
await assert.rejects(() => api.getCampaigns(1), ResponseError);
|
||||
});
|
||||
Reference in New Issue
Block a user