16
README.md
16
README.md
@@ -35,7 +35,6 @@ Check out my related projects:
|
||||
- [bybit-api](https://www.npmjs.com/package/bybit-api)
|
||||
- [okx-api](https://www.npmjs.com/package/okx-api)
|
||||
- [bitget-api](https://www.npmjs.com/package/bitget-api)
|
||||
- [ftx-api](https://www.npmjs.com/package/ftx-api)
|
||||
- Try my misc utilities:
|
||||
- [orderbooks](https://www.npmjs.com/package/orderbooks)
|
||||
- Check out my examples:
|
||||
@@ -55,27 +54,27 @@ The version on npm is the output from the `build` command and can be used in pro
|
||||
|
||||
- [src](./src) - the whole connector written in TypeScript
|
||||
- [lib](./lib) - the JavaScript version of the project (built from TypeScript). This should not be edited directly, as it will be overwritten with each release.
|
||||
- [dist](./dist) - the webpack bundle of the project for use in browser environments (see guidance on webpack below).
|
||||
- [examples](./examples) - some implementation examples & demonstrations. Contributions are welcome!
|
||||
- [examples](./examples) - examples & demonstrations. Contributions are welcome!
|
||||
|
||||
---
|
||||
|
||||
## REST API Clients
|
||||
|
||||
Bybit has several API groups (originally one per product). Each generation is labelled with the version number (e.g. v1/v2/v3/v5). Some of the newer API groups can only be used by upgrading your account to the unified account, but doing so will prevent you from using the V1 and V2 APIs.
|
||||
Bybit has several API groups (originally one per product). Each generation is labelled with the version number (e.g. v1/v2/v3/v5). New projects & developments should use the newest available API generation (e.g. use the V5 APIs instead of V3).
|
||||
|
||||
Refer to the [V5 upgrade guide](https://bybit-exchange.github.io/docs/v5/upgrade-guide) for more information on requirements to use each API group. If you have a choice, you should use the newest generation that is available (e.g. use the V5 instead of the V3 APIs if you can).
|
||||
Refer to the [V5 interface mapping page](https://bybit-exchange.github.io/docs/v5/intro#v5-and-v3-interface-mapping-list) for more information on which V5 endpoints can be used instead of previous V3 endpoints.
|
||||
|
||||
Here are the available REST clients and the corresponding API groups described in the documentation:
|
||||
|
||||
| Class | Description |
|
||||
|:------------------------------------------------------------------: |:----------------------------------------------------------------------------------------------------------------------------: |
|
||||
| :------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
|
||||
| [ **V5 API** ] | The new unified V5 APIs (successor to previously fragmented APIs for all API groups). To learn more about the V5 API, please read the [V5 upgrade guideline](https://bybit-exchange.github.io/docs/v5/upgrade-guide). |
|
||||
| [RestClientV5](src/rest-client-v5.ts) | Unified V5 all-in-one REST client for all [V5 REST APIs](https://bybit-exchange.github.io/docs/v5/intro) |
|
||||
| [ **Derivatives v3** ] | The Derivatives v3 APIs (successor to the Futures V2 APIs) |
|
||||
| [UnifiedMarginClient](src/unified-margin-client.ts) | [Derivatives (v3) Unified Margin APIs](https://bybit-exchange.github.io/docs/derivatives/unified/place-order) |
|
||||
| [ContractClient](src/contract-client.ts) | [Derivatives (v3) Contract APIs](https://bybit-exchange.github.io/docs/derivatives/contract/place-order). |
|
||||
| [ **Futures v2** ] | The Futures v2 APIs |
|
||||
| Deprecated! ContractClient or RestClientV5 recommended | Please read the [V5 upgrade guideline](https://bybit-exchange.github.io/docs/v5/upgrade-guide) |
|
||||
| Deprecated! RestClientV5 recommended | Please read the [V5 interface mapping page](https://bybit-exchange.github.io/docs/v5/intro#v5-and-v3-interface-mapping-list) |
|
||||
| [~InverseClient~](src/inverse-client.ts) | [Inverse Perpetual Futures (v2) APIs](https://bybit-exchange.github.io/docs/futuresV2/inverse/) |
|
||||
| [~LinearClient~](src/linear-client.ts) | [USDT Perpetual Futures (v2) APIs](https://bybit-exchange.github.io/docs/futuresV2/linear/#t-introduction) |
|
||||
| [~InverseFuturesClient~](src/inverse-futures-client.ts) | [Inverse Futures (v2) APIs](https://bybit-exchange.github.io/docs/futuresV2/inverse_futures/#t-introduction) |
|
||||
@@ -256,6 +255,9 @@ const wsConfig = {
|
||||
// how long to wait before attempting to reconnect (in ms) after connection is closed
|
||||
// reconnectTimeout: 500,
|
||||
|
||||
// recv window size for authenticated websocket requests (higher latency connections (VPN) can cause authentication to fail if the recv window is too small)
|
||||
// recvWindow: 5000,
|
||||
|
||||
// config options sent to RestClient (used for time sync). See RestClient docs.
|
||||
// restOptions: { },
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bybit-api",
|
||||
"version": "3.7.7",
|
||||
"version": "3.7.8",
|
||||
"description": "Complete & robust Node.js SDK for Bybit's REST APIs and WebSockets, with TypeScript & strong end to end tests.",
|
||||
"main": "lib/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
|
||||
@@ -98,6 +98,8 @@ export interface WSClientConfigurableOptions {
|
||||
pongTimeout?: number;
|
||||
pingInterval?: number;
|
||||
reconnectTimeout?: number;
|
||||
/** Override the recv window for authenticating over websockets (default: 5000 ms) */
|
||||
recvWindow?: number;
|
||||
restOptions?: RestClientOptions;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
requestOptions?: any;
|
||||
|
||||
@@ -123,6 +123,7 @@ export class WebsocketClient extends EventEmitter {
|
||||
pongTimeout: 1000,
|
||||
pingInterval: 10000,
|
||||
reconnectTimeout: 500,
|
||||
recvWindow: 5000,
|
||||
fetchTimeOffsetBeforeAuth: false,
|
||||
...options,
|
||||
};
|
||||
@@ -739,7 +740,9 @@ export class WebsocketClient extends EventEmitter {
|
||||
? (await this.restClient?.fetchTimeOffset()) || 0
|
||||
: 0;
|
||||
|
||||
const signatureExpiresAt = Date.now() + timeOffset + 5000;
|
||||
const recvWindow = this.options.recvWindow || 5000;
|
||||
|
||||
const signatureExpiresAt = Date.now() + timeOffset + recvWindow;
|
||||
|
||||
const signature = await signMessage(
|
||||
'GET/realtime' + signatureExpiresAt,
|
||||
|
||||
@@ -2,7 +2,7 @@ import { SpotClient } from '../../src';
|
||||
import { getTestProxy } from '../proxy.util';
|
||||
import { errorResponseObject, successResponseList } from '../response.util';
|
||||
|
||||
describe('Private Spot REST API GET Endpoints', () => {
|
||||
describe.skip('Private Spot REST API GET Endpoints', () => {
|
||||
const API_KEY = process.env.API_KEY_COM;
|
||||
const API_SECRET = process.env.API_SECRET_COM;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { API_ERROR_CODE, SpotClient } from '../../src';
|
||||
import { getTestProxy } from '../proxy.util';
|
||||
import { successResponseObject } from '../response.util';
|
||||
|
||||
describe('Private Spot REST API POST Endpoints', () => {
|
||||
describe.skip('Private Spot REST API POST Endpoints', () => {
|
||||
const API_KEY = process.env.API_KEY_COM;
|
||||
const API_SECRET = process.env.API_SECRET_COM;
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
waitForSocketEvent,
|
||||
} from '../ws.util';
|
||||
|
||||
describe('Private Spot V1 Websocket Client', () => {
|
||||
describe.skip('Private Spot V1 Websocket Client', () => {
|
||||
let wsClient: WebsocketClient;
|
||||
const API_KEY = process.env.API_KEY_COM;
|
||||
const API_SECRET = process.env.API_SECRET_COM;
|
||||
@@ -34,7 +34,7 @@ describe('Private Spot V1 Websocket Client', () => {
|
||||
wsClient = new WebsocketClient(
|
||||
wsClientOptions,
|
||||
// fullLogger
|
||||
getSilentLogger('expectSuccess')
|
||||
getSilentLogger('expectSuccess'),
|
||||
);
|
||||
logAllEvents(wsClient);
|
||||
});
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
waitForSocketEvent,
|
||||
} from '../ws.util';
|
||||
|
||||
describe('Public Spot V1 Websocket Client', () => {
|
||||
describe.skip('Public Spot V1 Websocket Client', () => {
|
||||
let wsClient: WebsocketClient;
|
||||
|
||||
const wsClientOptions: WSClientConfigurableOptions = {
|
||||
@@ -23,7 +23,7 @@ describe('Public Spot V1 Websocket Client', () => {
|
||||
beforeAll(() => {
|
||||
wsClient = new WebsocketClient(
|
||||
wsClientOptions,
|
||||
getSilentLogger('expectSuccess')
|
||||
getSilentLogger('expectSuccess'),
|
||||
);
|
||||
wsClient.connectPublic();
|
||||
// logAllEvents(wsClient);
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
import { USDCOptionClient } from '../../../src';
|
||||
import { getTestProxy } from '../../proxy.util';
|
||||
import { successResponseObjectV3 } from '../../response.util';
|
||||
|
||||
describe('Private USDC Options REST API GET Endpoints', () => {
|
||||
const API_KEY = process.env.API_KEY_COM;
|
||||
const API_SECRET = process.env.API_SECRET_COM;
|
||||
const symbol = 'BTC-30SEP22-400000-C';
|
||||
|
||||
it('should have api credentials to test with', () => {
|
||||
expect(API_KEY).toStrictEqual(expect.any(String));
|
||||
expect(API_SECRET).toStrictEqual(expect.any(String));
|
||||
});
|
||||
|
||||
const api = new USDCOptionClient(
|
||||
{
|
||||
key: API_KEY,
|
||||
secret: API_SECRET,
|
||||
testnet: false,
|
||||
},
|
||||
getTestProxy(),
|
||||
);
|
||||
const category = 'OPTION';
|
||||
|
||||
it('getActiveRealtimeOrders()', async () => {
|
||||
expect(await api.getActiveRealtimeOrders()).toMatchObject(
|
||||
successResponseObjectV3(),
|
||||
);
|
||||
});
|
||||
|
||||
it('getActiveOrders()', async () => {
|
||||
expect(await api.getActiveOrders({ category })).toMatchObject(
|
||||
successResponseObjectV3(),
|
||||
);
|
||||
});
|
||||
|
||||
it('getHistoricOrders()', async () => {
|
||||
expect(await api.getHistoricOrders({ category })).toMatchObject(
|
||||
successResponseObjectV3(),
|
||||
);
|
||||
});
|
||||
|
||||
it('getOrderExecutionHistory()', async () => {
|
||||
expect(await api.getOrderExecutionHistory({ category })).toMatchObject(
|
||||
successResponseObjectV3(),
|
||||
);
|
||||
});
|
||||
|
||||
it('getTransactionLog()', async () => {
|
||||
expect(await api.getTransactionLog({ type: 'TRADE' })).toMatchObject(
|
||||
successResponseObjectV3(),
|
||||
);
|
||||
});
|
||||
|
||||
it('getBalances()', async () => {
|
||||
expect(await api.getBalances()).toMatchObject(successResponseObjectV3());
|
||||
});
|
||||
|
||||
it('getAssetInfo()', async () => {
|
||||
expect(await api.getAssetInfo()).toMatchObject(successResponseObjectV3());
|
||||
});
|
||||
|
||||
it('getMarginMode()', async () => {
|
||||
expect(await api.getMarginMode()).toMatchObject(successResponseObjectV3());
|
||||
});
|
||||
|
||||
it('getPositions()', async () => {
|
||||
expect(await api.getPositions({ category })).toMatchObject(
|
||||
successResponseObjectV3(),
|
||||
);
|
||||
});
|
||||
|
||||
it('getDeliveryHistory()', async () => {
|
||||
expect(await api.getDeliveryHistory({ symbol })).toMatchObject(
|
||||
successResponseObjectV3(),
|
||||
);
|
||||
});
|
||||
|
||||
it('getPositionsInfoUponExpiry()', async () => {
|
||||
expect(await api.getPositionsInfoUponExpiry()).toMatchObject(
|
||||
successResponseObjectV3(),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,169 +0,0 @@
|
||||
import { API_ERROR_CODE, USDCOptionClient } from '../../../src';
|
||||
import { getTestProxy } from '../../proxy.util';
|
||||
// import { successResponseObjectV3 } from '../../response.util';
|
||||
|
||||
describe('Private USDC Options REST API POST Endpoints', () => {
|
||||
const API_KEY = process.env.API_KEY_COM;
|
||||
const API_SECRET = process.env.API_SECRET_COM;
|
||||
|
||||
it('should have api credentials to test with', () => {
|
||||
expect(API_KEY).toStrictEqual(expect.any(String));
|
||||
expect(API_SECRET).toStrictEqual(expect.any(String));
|
||||
});
|
||||
|
||||
const api = new USDCOptionClient(
|
||||
{
|
||||
key: API_KEY,
|
||||
secret: API_SECRET,
|
||||
testnet: false,
|
||||
},
|
||||
getTestProxy(),
|
||||
);
|
||||
|
||||
const currency = 'USDC';
|
||||
const symbol = 'BTC-30SEP22-400000-C';
|
||||
|
||||
it('submitOrder()', async () => {
|
||||
expect(
|
||||
await api.submitOrder({
|
||||
symbol,
|
||||
orderType: 'Limit',
|
||||
side: 'Sell',
|
||||
orderQty: '1000',
|
||||
orderPrice: '40',
|
||||
orderLinkId: Date.now().toString(),
|
||||
timeInForce: 'GoodTillCancel',
|
||||
}),
|
||||
).toMatchObject({
|
||||
retCode: API_ERROR_CODE.CONTRACT_NAME_NOT_EXIST,
|
||||
});
|
||||
});
|
||||
|
||||
it('batchSubmitOrders()', async () => {
|
||||
expect(
|
||||
await api.batchSubmitOrders([
|
||||
{
|
||||
symbol,
|
||||
orderType: 'Limit',
|
||||
side: 'Sell',
|
||||
orderQty: '1000',
|
||||
orderPrice: '40',
|
||||
orderLinkId: Date.now().toString(),
|
||||
timeInForce: 'GoodTillCancel',
|
||||
},
|
||||
{
|
||||
symbol,
|
||||
orderType: 'Limit',
|
||||
side: 'Sell',
|
||||
orderQty: '1000',
|
||||
orderPrice: '40',
|
||||
orderLinkId: Date.now().toString(),
|
||||
timeInForce: 'GoodTillCancel',
|
||||
},
|
||||
]),
|
||||
).toMatchObject({
|
||||
result: [
|
||||
{ errorCode: API_ERROR_CODE.CONTRACT_NAME_NOT_EXIST },
|
||||
{ errorCode: API_ERROR_CODE.CONTRACT_NAME_NOT_EXIST },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('modifyOrder()', async () => {
|
||||
expect(
|
||||
await api.modifyOrder({
|
||||
symbol,
|
||||
orderId: 'somethingFake',
|
||||
}),
|
||||
).toMatchObject({
|
||||
retCode: API_ERROR_CODE.CONTRACT_NAME_NOT_EXIST,
|
||||
});
|
||||
});
|
||||
|
||||
it('batchModifyOrders()', async () => {
|
||||
expect(
|
||||
await api.batchModifyOrders([
|
||||
{
|
||||
symbol,
|
||||
orderId: 'somethingFake1',
|
||||
},
|
||||
{
|
||||
symbol,
|
||||
orderId: 'somethingFake2',
|
||||
},
|
||||
]),
|
||||
).toMatchObject({
|
||||
result: [
|
||||
{ errorCode: API_ERROR_CODE.CONTRACT_NAME_NOT_EXIST },
|
||||
{ errorCode: API_ERROR_CODE.CONTRACT_NAME_NOT_EXIST },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('cancelOrder()', async () => {
|
||||
expect(
|
||||
await api.cancelOrder({
|
||||
symbol,
|
||||
orderId: 'somethingFake1',
|
||||
}),
|
||||
).toMatchObject({
|
||||
retCode: API_ERROR_CODE.CONTRACT_NAME_NOT_EXIST,
|
||||
});
|
||||
});
|
||||
|
||||
it('batchCancelOrders()', async () => {
|
||||
expect(
|
||||
await api.batchCancelOrders([
|
||||
{
|
||||
symbol,
|
||||
orderId: 'somethingFake1',
|
||||
},
|
||||
{
|
||||
symbol,
|
||||
orderId: 'somethingFake2',
|
||||
},
|
||||
]),
|
||||
).toMatchObject({
|
||||
result: [
|
||||
{ errorCode: API_ERROR_CODE.CONTRACT_NAME_NOT_EXIST },
|
||||
{ errorCode: API_ERROR_CODE.CONTRACT_NAME_NOT_EXIST },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('cancelActiveOrders()', async () => {
|
||||
expect(await api.cancelActiveOrders()).toMatchObject({
|
||||
retCode: API_ERROR_CODE.NO_ACTIVE_ORDER,
|
||||
});
|
||||
});
|
||||
|
||||
// Reached out to bybit
|
||||
it.skip('setMarginMode()', async () => {
|
||||
expect(await api.setMarginMode('REGULAR_MARGIN')).toMatchObject(
|
||||
{
|
||||
retCode: API_ERROR_CODE.SET_MARGIN_MODE_FAILED_USDC,
|
||||
},
|
||||
// successResponseObjectV3()
|
||||
);
|
||||
});
|
||||
|
||||
it('modifyMMP()', async () => {
|
||||
expect(
|
||||
await api.modifyMMP({
|
||||
currency,
|
||||
windowMs: 0,
|
||||
frozenPeriodMs: 100,
|
||||
qtyLimit: '100',
|
||||
deltaLimit: '1',
|
||||
}),
|
||||
).toMatchObject({
|
||||
retCode: API_ERROR_CODE.INCORRECT_MMP_PARAMETERS,
|
||||
});
|
||||
});
|
||||
|
||||
it('resetMMP()', async () => {
|
||||
expect(await api.resetMMP(currency)).toMatchObject({
|
||||
retCode: API_ERROR_CODE.INSTITION_MMP_PROFILE_NOT_FOUND,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,61 +0,0 @@
|
||||
import { USDCOptionClient } from '../../../src';
|
||||
import { getTestProxy } from '../../proxy.util';
|
||||
import {
|
||||
successResponseObject,
|
||||
successResponseObjectV3,
|
||||
} from '../../response.util';
|
||||
|
||||
describe('Public USDC Options REST API Endpoints', () => {
|
||||
const API_KEY = undefined;
|
||||
const API_SECRET = undefined;
|
||||
|
||||
const api = new USDCOptionClient(
|
||||
{
|
||||
key: API_KEY,
|
||||
secret: API_SECRET,
|
||||
testnet: false,
|
||||
},
|
||||
getTestProxy(),
|
||||
);
|
||||
const symbol = 'BTC-30SEP22-400000-C';
|
||||
|
||||
it('getOrderBook()', async () => {
|
||||
expect(await api.getOrderBook(symbol)).toMatchObject(
|
||||
successResponseObjectV3(),
|
||||
);
|
||||
});
|
||||
|
||||
it('getContractInfo()', async () => {
|
||||
expect(await api.getContractInfo()).toMatchObject(
|
||||
successResponseObjectV3(),
|
||||
);
|
||||
});
|
||||
|
||||
it('getSymbolTicker()', async () => {
|
||||
expect(await api.getSymbolTicker(symbol)).toMatchObject(
|
||||
successResponseObjectV3(),
|
||||
);
|
||||
});
|
||||
|
||||
it('getDeliveryPrice()', async () => {
|
||||
expect(await api.getDeliveryPrice()).toMatchObject(
|
||||
successResponseObjectV3(),
|
||||
);
|
||||
});
|
||||
|
||||
it('getLast500Trades()', async () => {
|
||||
expect(await api.getLast500Trades({ category: 'OPTION' })).toMatchObject(
|
||||
successResponseObjectV3(),
|
||||
);
|
||||
});
|
||||
|
||||
it('getHistoricalVolatility()', async () => {
|
||||
expect(await api.getHistoricalVolatility()).toMatchObject(
|
||||
successResponseObjectV3(),
|
||||
);
|
||||
});
|
||||
|
||||
it('getServerTime()', async () => {
|
||||
expect(await api.getServerTime()).toMatchObject(successResponseObject());
|
||||
});
|
||||
});
|
||||
@@ -1,151 +0,0 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import {
|
||||
WSClientConfigurableOptions,
|
||||
WS_ERROR_ENUM,
|
||||
WS_KEY_MAP,
|
||||
WebsocketClient,
|
||||
} from '../../../src';
|
||||
import {
|
||||
WS_OPEN_EVENT_PARTIAL,
|
||||
fullLogger,
|
||||
getSilentLogger,
|
||||
logAllEvents,
|
||||
waitForSocketEvent,
|
||||
} from '../../ws.util';
|
||||
|
||||
describe('Private USDC Option Websocket Client', () => {
|
||||
const API_KEY = process.env.API_KEY_COM;
|
||||
const API_SECRET = process.env.API_SECRET_COM;
|
||||
|
||||
const wsClientOptions: WSClientConfigurableOptions = {
|
||||
market: 'usdcOption',
|
||||
key: API_KEY,
|
||||
secret: API_SECRET,
|
||||
};
|
||||
|
||||
const wsTopic = 'user.openapi.option.position';
|
||||
|
||||
describe('with invalid credentials', () => {
|
||||
it('should reject private subscribe if keys/signature are incorrect', async () => {
|
||||
const badClient = new WebsocketClient(
|
||||
{
|
||||
...wsClientOptions,
|
||||
key: 'bad',
|
||||
secret: 'bad',
|
||||
reconnectTimeout: 10000,
|
||||
},
|
||||
// fullLogger
|
||||
getSilentLogger('expect401')
|
||||
);
|
||||
// logAllEvents(badClient);
|
||||
|
||||
// const wsOpenPromise = waitForSocketEvent(badClient, 'open');
|
||||
const wsResponsePromise = waitForSocketEvent(badClient, 'response');
|
||||
// const wsUpdatePromise = waitForSocketEvent(wsClient, 'update');
|
||||
|
||||
badClient.connectPrivate();
|
||||
|
||||
const responsePartial = {
|
||||
ret_msg: WS_ERROR_ENUM.USDC_OPTION_AUTH_FAILED,
|
||||
success: false,
|
||||
type: 'AUTH_RESP',
|
||||
};
|
||||
expect(wsResponsePromise).rejects.toMatchObject(responsePartial);
|
||||
|
||||
try {
|
||||
await Promise.all([wsResponsePromise]);
|
||||
} catch (e) {
|
||||
// console.error()
|
||||
expect(e).toMatchObject(responsePartial);
|
||||
}
|
||||
|
||||
// badClient.subscribe(wsTopic);
|
||||
badClient.removeAllListeners();
|
||||
badClient.closeAll(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with valid API credentails', () => {
|
||||
let wsClient: WebsocketClient;
|
||||
|
||||
it('should have api credentials to test with', () => {
|
||||
expect(API_KEY).toStrictEqual(expect.any(String));
|
||||
expect(API_SECRET).toStrictEqual(expect.any(String));
|
||||
});
|
||||
|
||||
beforeAll(() => {
|
||||
wsClient = new WebsocketClient(
|
||||
wsClientOptions,
|
||||
getSilentLogger('expectSuccess')
|
||||
);
|
||||
// logAllEvents(wsClient);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
wsClient.closeAll(true);
|
||||
});
|
||||
|
||||
it('should open a private ws connection', async () => {
|
||||
wsClient.connectPrivate();
|
||||
const wsOpenPromise = waitForSocketEvent(wsClient, 'open');
|
||||
const wsResponsePromise = waitForSocketEvent(wsClient, 'response');
|
||||
|
||||
try {
|
||||
expect(await wsOpenPromise).toMatchObject({
|
||||
event: WS_OPEN_EVENT_PARTIAL,
|
||||
wsKey: WS_KEY_MAP.usdcOptionPrivate,
|
||||
});
|
||||
} catch (e) {
|
||||
expect(e).toBeFalsy();
|
||||
}
|
||||
|
||||
try {
|
||||
expect(await wsResponsePromise).toMatchObject({
|
||||
ret_msg: '0',
|
||||
success: true,
|
||||
type: 'AUTH_RESP',
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(`Wait for "${wsTopic}" event exception: `, e);
|
||||
expect(e).toBeFalsy();
|
||||
}
|
||||
});
|
||||
|
||||
it(`should subscribe to private "${wsTopic}" events`, async () => {
|
||||
wsClient.connectPrivate();
|
||||
const wsResponsePromise = waitForSocketEvent(wsClient, 'response');
|
||||
const wsUpdatePromise = waitForSocketEvent(wsClient, 'update');
|
||||
|
||||
// expect(wsUpdatePromise).resolves.toStrictEqual('');
|
||||
wsClient.subscribe(wsTopic);
|
||||
|
||||
try {
|
||||
expect(await wsResponsePromise).toMatchObject({
|
||||
data: {
|
||||
failTopics: [],
|
||||
successTopics: [wsTopic],
|
||||
},
|
||||
success: true,
|
||||
type: 'COMMAND_RESP',
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(
|
||||
`Wait for "${wsTopic}" subscription response exception: `,
|
||||
e
|
||||
);
|
||||
expect(e).toBeFalsy();
|
||||
}
|
||||
expect(await wsUpdatePromise).toMatchObject({
|
||||
creationTime: expect.any(Number),
|
||||
data: {
|
||||
baseLine: expect.any(Number),
|
||||
dataType: expect.any(String),
|
||||
result: expect.any(Array),
|
||||
version: expect.any(Number),
|
||||
},
|
||||
topic: wsTopic,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,78 +0,0 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import {
|
||||
WSClientConfigurableOptions,
|
||||
WS_KEY_MAP,
|
||||
WebsocketClient,
|
||||
} from '../../../src';
|
||||
import {
|
||||
WS_OPEN_EVENT_PARTIAL,
|
||||
getSilentLogger,
|
||||
logAllEvents,
|
||||
waitForSocketEvent,
|
||||
} from '../../ws.util';
|
||||
|
||||
describe('Public USDC Option Websocket Client', () => {
|
||||
let wsClient: WebsocketClient;
|
||||
|
||||
const wsClientOptions: WSClientConfigurableOptions = {
|
||||
market: 'usdcOption',
|
||||
};
|
||||
|
||||
beforeAll(() => {
|
||||
wsClient = new WebsocketClient(
|
||||
wsClientOptions,
|
||||
getSilentLogger('expectSuccessNoAuth')
|
||||
);
|
||||
wsClient.connectPublic();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
wsClient.closeAll(true);
|
||||
});
|
||||
|
||||
it('should open a public ws connection', async () => {
|
||||
const wsOpenPromise = waitForSocketEvent(wsClient, 'open');
|
||||
try {
|
||||
expect(await wsOpenPromise).toMatchObject({
|
||||
event: WS_OPEN_EVENT_PARTIAL,
|
||||
wsKey: WS_KEY_MAP.usdcOptionPublic,
|
||||
});
|
||||
} catch (e) {
|
||||
expect(e).toBeFalsy();
|
||||
}
|
||||
});
|
||||
|
||||
it('should subscribe to public trade events', async () => {
|
||||
const wsResponsePromise = waitForSocketEvent(wsClient, 'response');
|
||||
// const wsUpdatePromise = waitForSocketEvent(wsClient, 'update');
|
||||
|
||||
wsClient.subscribe([
|
||||
'recenttrades.BTC',
|
||||
'recenttrades.ETH',
|
||||
'recenttrades.SOL',
|
||||
]);
|
||||
|
||||
try {
|
||||
expect(await wsResponsePromise).toMatchObject({
|
||||
success: true,
|
||||
data: {
|
||||
failTopics: [],
|
||||
successTopics: expect.any(Array),
|
||||
},
|
||||
type: 'COMMAND_RESP',
|
||||
});
|
||||
} catch (e) {
|
||||
// sub failed
|
||||
expect(e).toBeFalsy();
|
||||
}
|
||||
|
||||
// Takes a while to get an event from USDC options - testing this manually for now
|
||||
// try {
|
||||
// expect(await wsUpdatePromise).toStrictEqual('asdfasdf');
|
||||
// } catch (e) {
|
||||
// // no data
|
||||
// expect(e).toBeFalsy();
|
||||
// }
|
||||
});
|
||||
});
|
||||
@@ -1,79 +0,0 @@
|
||||
import { USDCPerpetualClient } from '../../../src';
|
||||
import { getTestProxy } from '../../proxy.util';
|
||||
import { successResponseObjectV3 } from '../../response.util';
|
||||
|
||||
describe('Private USDC Perp REST API GET Endpoints', () => {
|
||||
const API_KEY = process.env.API_KEY_COM;
|
||||
const API_SECRET = process.env.API_SECRET_COM;
|
||||
|
||||
it('should have api credentials to test with', () => {
|
||||
expect(API_KEY).toStrictEqual(expect.any(String));
|
||||
expect(API_SECRET).toStrictEqual(expect.any(String));
|
||||
});
|
||||
|
||||
const api = new USDCPerpetualClient(
|
||||
{
|
||||
key: API_KEY,
|
||||
secret: API_SECRET,
|
||||
testnet: false,
|
||||
},
|
||||
getTestProxy(),
|
||||
);
|
||||
|
||||
const symbol = 'BTCPERP';
|
||||
const category = 'PERPETUAL';
|
||||
|
||||
it('getActiveOrders()', async () => {
|
||||
expect(await api.getActiveOrders({ category })).toMatchObject(
|
||||
successResponseObjectV3(),
|
||||
);
|
||||
});
|
||||
|
||||
it('getHistoricOrders()', async () => {
|
||||
expect(await api.getHistoricOrders({ category })).toMatchObject(
|
||||
successResponseObjectV3(),
|
||||
);
|
||||
});
|
||||
|
||||
it('getOrderExecutionHistory()', async () => {
|
||||
expect(await api.getOrderExecutionHistory({ category })).toMatchObject(
|
||||
successResponseObjectV3(),
|
||||
);
|
||||
});
|
||||
|
||||
it('getTransactionLog()', async () => {
|
||||
expect(await api.getTransactionLog({ type: 'TRADE' })).toMatchObject(
|
||||
successResponseObjectV3(),
|
||||
);
|
||||
});
|
||||
|
||||
it('getBalances()', async () => {
|
||||
expect(await api.getBalances()).toMatchObject(successResponseObjectV3());
|
||||
});
|
||||
|
||||
it('getAssetInfo()', async () => {
|
||||
expect(await api.getAssetInfo()).toMatchObject(successResponseObjectV3());
|
||||
});
|
||||
|
||||
it('getMarginMode()', async () => {
|
||||
expect(await api.getMarginMode()).toMatchObject(successResponseObjectV3());
|
||||
});
|
||||
|
||||
it('getPositions()', async () => {
|
||||
expect(await api.getPositions({ category })).toMatchObject(
|
||||
successResponseObjectV3(),
|
||||
);
|
||||
});
|
||||
|
||||
it('getSettlementHistory()', async () => {
|
||||
expect(await api.getSettlementHistory({ symbol })).toMatchObject(
|
||||
successResponseObjectV3(),
|
||||
);
|
||||
});
|
||||
|
||||
it('getPredictedFundingRate()', async () => {
|
||||
expect(await api.getPredictedFundingRate(symbol)).toMatchObject(
|
||||
successResponseObjectV3(),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,95 +0,0 @@
|
||||
import { API_ERROR_CODE, USDCPerpetualClient } from '../../../src';
|
||||
import { getTestProxy } from '../../proxy.util';
|
||||
import { successEmptyResponseObjectV3 } from '../../response.util';
|
||||
|
||||
describe('Private USDC Perp REST API POST Endpoints', () => {
|
||||
const API_KEY = process.env.API_KEY_COM;
|
||||
const API_SECRET = process.env.API_SECRET_COM;
|
||||
|
||||
it('should have api credentials to test with', () => {
|
||||
expect(API_KEY).toStrictEqual(expect.any(String));
|
||||
expect(API_SECRET).toStrictEqual(expect.any(String));
|
||||
});
|
||||
|
||||
const api = new USDCPerpetualClient(
|
||||
{
|
||||
key: API_KEY,
|
||||
secret: API_SECRET,
|
||||
testnet: false,
|
||||
},
|
||||
getTestProxy(),
|
||||
);
|
||||
|
||||
const symbol = 'BTCPERP';
|
||||
|
||||
it('submitOrder()', async () => {
|
||||
expect(
|
||||
await api.submitOrder({
|
||||
symbol,
|
||||
side: 'Sell',
|
||||
orderType: 'Limit',
|
||||
orderFilter: 'Order',
|
||||
orderQty: '1',
|
||||
orderPrice: '20000',
|
||||
orderLinkId: Date.now().toString(),
|
||||
timeInForce: 'GoodTillCancel',
|
||||
}),
|
||||
).toMatchObject({
|
||||
retCode: API_ERROR_CODE.INSUFFICIENT_BALANCE_FOR_ORDER_COST,
|
||||
});
|
||||
});
|
||||
|
||||
it('modifyOrder()', async () => {
|
||||
expect(
|
||||
await api.modifyOrder({
|
||||
symbol,
|
||||
orderId: 'somethingFake',
|
||||
orderPrice: '20000',
|
||||
orderFilter: 'Order',
|
||||
}),
|
||||
).toMatchObject({
|
||||
retCode: API_ERROR_CODE.ORDER_NOT_FOUND_OR_TOO_LATE,
|
||||
});
|
||||
});
|
||||
|
||||
it('cancelOrder()', async () => {
|
||||
expect(
|
||||
await api.cancelOrder({
|
||||
symbol,
|
||||
orderId: 'somethingFake1',
|
||||
orderFilter: 'Order',
|
||||
}),
|
||||
).toMatchObject({
|
||||
retCode: API_ERROR_CODE.ORDER_NOT_FOUND_OR_TOO_LATE,
|
||||
});
|
||||
});
|
||||
|
||||
it('cancelActiveOrders()', async () => {
|
||||
expect(await api.cancelActiveOrders(symbol, 'Order')).toMatchObject(
|
||||
successEmptyResponseObjectV3(),
|
||||
);
|
||||
});
|
||||
|
||||
// Reached out to bybit
|
||||
it.skip('setMarginMode()', async () => {
|
||||
expect(await api.setMarginMode('REGULAR_MARGIN')).toMatchObject(
|
||||
{
|
||||
retCode: API_ERROR_CODE.SET_MARGIN_MODE_FAILED_USDC,
|
||||
retMsg: '',
|
||||
},
|
||||
// successResponseObjectV3()
|
||||
);
|
||||
});
|
||||
|
||||
it('setLeverage()', async () => {
|
||||
expect(await api.setLeverage(symbol, '5')).toMatchObject({
|
||||
retCode: API_ERROR_CODE.LEVERAGE_NOT_MODIFIED,
|
||||
});
|
||||
});
|
||||
|
||||
it('setRiskLimit()', async () => {
|
||||
expect(await api.setRiskLimit(symbol, 1)).toMatchObject({
|
||||
retCode: API_ERROR_CODE.RISK_LIMIT_NOT_EXISTS,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,102 +0,0 @@
|
||||
import { USDCKlineRequest, USDCPerpetualClient } from '../../../src';
|
||||
import {
|
||||
successResponseObject,
|
||||
successResponseObjectV3,
|
||||
} from '../../response.util';
|
||||
import { getTestProxy } from '../../proxy.util';
|
||||
|
||||
describe('Public USDC Perp REST API Endpoints', () => {
|
||||
const API_KEY = undefined;
|
||||
const API_SECRET = undefined;
|
||||
|
||||
const api = new USDCPerpetualClient(
|
||||
{
|
||||
key: API_KEY,
|
||||
secret: API_SECRET,
|
||||
testnet: false,
|
||||
},
|
||||
getTestProxy(),
|
||||
);
|
||||
|
||||
const symbol = 'BTCPERP';
|
||||
const category = 'PERPETUAL';
|
||||
const startTime = Number((Date.now() / 1000).toFixed(0));
|
||||
|
||||
const candleRequest: USDCKlineRequest = { symbol, period: '1m', startTime };
|
||||
|
||||
it('getOrderBook()', async () => {
|
||||
expect(await api.getOrderBook(symbol)).toMatchObject(
|
||||
successResponseObjectV3(),
|
||||
);
|
||||
});
|
||||
|
||||
it('getContractInfo()', async () => {
|
||||
expect(await api.getContractInfo()).toMatchObject(
|
||||
successResponseObjectV3(),
|
||||
);
|
||||
});
|
||||
|
||||
it('getSymbolTicker()', async () => {
|
||||
expect(await api.getSymbolTicker(symbol)).toMatchObject(
|
||||
successResponseObjectV3(),
|
||||
);
|
||||
});
|
||||
|
||||
it('getCandles()', async () => {
|
||||
expect(await api.getCandles(candleRequest)).toMatchObject(
|
||||
successResponseObjectV3(),
|
||||
);
|
||||
});
|
||||
|
||||
it('getMarkPrice()', async () => {
|
||||
expect(await api.getMarkPrice(candleRequest)).toMatchObject(
|
||||
successResponseObjectV3(),
|
||||
);
|
||||
});
|
||||
|
||||
it('getIndexPrice()', async () => {
|
||||
expect(await api.getIndexPrice(candleRequest)).toMatchObject(
|
||||
successResponseObjectV3(),
|
||||
);
|
||||
});
|
||||
|
||||
it('getIndexPremium()', async () => {
|
||||
expect(await api.getIndexPremium(candleRequest)).toMatchObject(
|
||||
successResponseObjectV3(),
|
||||
);
|
||||
});
|
||||
|
||||
it('getOpenInterest()', async () => {
|
||||
expect(await api.getOpenInterest({ symbol, period: '1m' })).toMatchObject(
|
||||
successResponseObjectV3(),
|
||||
);
|
||||
});
|
||||
|
||||
it('getLargeOrders()', async () => {
|
||||
expect(await api.getLargeOrders({ symbol })).toMatchObject(
|
||||
successResponseObjectV3(),
|
||||
);
|
||||
});
|
||||
|
||||
it('getLongShortRatio()', async () => {
|
||||
expect(await api.getLongShortRatio({ symbol, period: '1m' })).toMatchObject(
|
||||
successResponseObjectV3(),
|
||||
);
|
||||
});
|
||||
|
||||
it('getLast500Trades()', async () => {
|
||||
expect(await api.getLast500Trades({ category })).toMatchObject(
|
||||
successResponseObjectV3(),
|
||||
);
|
||||
});
|
||||
|
||||
it('getLastFundingRate()', async () => {
|
||||
expect(await api.getLastFundingRate(symbol)).toMatchObject(
|
||||
successResponseObjectV3(),
|
||||
);
|
||||
});
|
||||
|
||||
it('getServerTime()', async () => {
|
||||
expect(await api.getServerTime()).toMatchObject(successResponseObject());
|
||||
});
|
||||
});
|
||||
@@ -1,150 +0,0 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import {
|
||||
WSClientConfigurableOptions,
|
||||
WS_ERROR_ENUM,
|
||||
WS_KEY_MAP,
|
||||
WebsocketClient,
|
||||
} from '../../../src';
|
||||
import {
|
||||
WS_OPEN_EVENT_PARTIAL,
|
||||
fullLogger,
|
||||
getSilentLogger,
|
||||
logAllEvents,
|
||||
waitForSocketEvent,
|
||||
} from '../../ws.util';
|
||||
|
||||
describe('Private USDC Perp Websocket Client', () => {
|
||||
const API_KEY = process.env.API_KEY_COM;
|
||||
const API_SECRET = process.env.API_SECRET_COM;
|
||||
|
||||
const wsClientOptions: WSClientConfigurableOptions = {
|
||||
market: 'usdcPerp',
|
||||
key: API_KEY,
|
||||
secret: API_SECRET,
|
||||
};
|
||||
|
||||
const wsTopic = 'user.openapi.perp.position';
|
||||
|
||||
describe('with invalid credentials', () => {
|
||||
it('should reject private subscribe if keys/signature are incorrect', async () => {
|
||||
const badClient = new WebsocketClient(
|
||||
{
|
||||
...wsClientOptions,
|
||||
key: 'bad',
|
||||
secret: 'bad',
|
||||
reconnectTimeout: 10000,
|
||||
},
|
||||
// fullLogger
|
||||
getSilentLogger('expect401')
|
||||
);
|
||||
// logAllEvents(badClient);
|
||||
|
||||
// const wsOpenPromise = waitForSocketEvent(badClient, 'open');
|
||||
const wsResponsePromise = waitForSocketEvent(badClient, 'response');
|
||||
// const wsUpdatePromise = waitForSocketEvent(wsClient, 'update');
|
||||
|
||||
badClient.connectPrivate();
|
||||
|
||||
const responsePartial = {
|
||||
ret_msg: WS_ERROR_ENUM.USDC_OPTION_AUTH_FAILED,
|
||||
success: false,
|
||||
type: 'AUTH_RESP',
|
||||
};
|
||||
expect(wsResponsePromise).rejects.toMatchObject(responsePartial);
|
||||
|
||||
try {
|
||||
await Promise.all([wsResponsePromise]);
|
||||
} catch (e) {
|
||||
// console.error()
|
||||
expect(e).toMatchObject(responsePartial);
|
||||
}
|
||||
|
||||
// badClient.subscribe(wsTopic);
|
||||
badClient.removeAllListeners();
|
||||
badClient.closeAll(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with valid API credentails', () => {
|
||||
let wsClient: WebsocketClient;
|
||||
|
||||
it('should have api credentials to test with', () => {
|
||||
expect(API_KEY).toStrictEqual(expect.any(String));
|
||||
expect(API_SECRET).toStrictEqual(expect.any(String));
|
||||
});
|
||||
|
||||
beforeAll(() => {
|
||||
wsClient = new WebsocketClient(
|
||||
wsClientOptions,
|
||||
getSilentLogger('expectSuccess')
|
||||
);
|
||||
wsClient.connectPrivate();
|
||||
// logAllEvents(wsClient);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
wsClient.closeAll(true);
|
||||
});
|
||||
|
||||
it('should open a private ws connection', async () => {
|
||||
const wsOpenPromise = waitForSocketEvent(wsClient, 'open');
|
||||
const wsResponsePromise = waitForSocketEvent(wsClient, 'response');
|
||||
|
||||
try {
|
||||
expect(await wsOpenPromise).toMatchObject({
|
||||
event: WS_OPEN_EVENT_PARTIAL,
|
||||
wsKey: WS_KEY_MAP.usdcPerpPrivate,
|
||||
});
|
||||
} catch (e) {
|
||||
expect(e).toBeFalsy();
|
||||
}
|
||||
|
||||
try {
|
||||
expect(await wsResponsePromise).toMatchObject({
|
||||
ret_msg: '0',
|
||||
success: true,
|
||||
type: 'AUTH_RESP',
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(`Wait for "${wsTopic}" event exception: `, e);
|
||||
expect(e).toBeFalsy();
|
||||
}
|
||||
});
|
||||
|
||||
it(`should subscribe to private "${wsTopic}" events`, async () => {
|
||||
const wsResponsePromise = waitForSocketEvent(wsClient, 'response');
|
||||
const wsUpdatePromise = waitForSocketEvent(wsClient, 'update');
|
||||
|
||||
// expect(wsUpdatePromise).resolves.toStrictEqual('');
|
||||
wsClient.subscribe(wsTopic);
|
||||
|
||||
try {
|
||||
expect(await wsResponsePromise).toMatchObject({
|
||||
data: {
|
||||
failTopics: [],
|
||||
successTopics: [wsTopic],
|
||||
},
|
||||
success: true,
|
||||
type: 'COMMAND_RESP',
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(
|
||||
`Wait for "${wsTopic}" subscription response exception: `,
|
||||
e
|
||||
);
|
||||
expect(e).toBeFalsy();
|
||||
}
|
||||
expect(await wsUpdatePromise).toMatchObject({
|
||||
creationTime: expect.any(Number),
|
||||
data: {
|
||||
baseLine: expect.any(Number),
|
||||
dataType: expect.any(String),
|
||||
result: expect.any(Array),
|
||||
version: expect.any(Number),
|
||||
},
|
||||
topic: wsTopic,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,76 +0,0 @@
|
||||
import {
|
||||
WSClientConfigurableOptions,
|
||||
WS_KEY_MAP,
|
||||
WebsocketClient,
|
||||
} from '../../../src';
|
||||
import {
|
||||
WS_OPEN_EVENT_PARTIAL,
|
||||
getSilentLogger,
|
||||
waitForSocketEvent,
|
||||
} from '../../ws.util';
|
||||
|
||||
describe('Public USDC Perp Websocket Client', () => {
|
||||
let wsClient: WebsocketClient;
|
||||
|
||||
const wsClientOptions: WSClientConfigurableOptions = {
|
||||
market: 'usdcPerp',
|
||||
};
|
||||
|
||||
beforeAll(() => {
|
||||
wsClient = new WebsocketClient(
|
||||
wsClientOptions,
|
||||
getSilentLogger('expectSuccessNoAuth')
|
||||
);
|
||||
wsClient.connectPublic();
|
||||
// logAllEvents(wsClient);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
wsClient.closeAll(true);
|
||||
});
|
||||
|
||||
it('should open a public ws connection', async () => {
|
||||
const wsOpenPromise = waitForSocketEvent(wsClient, 'open');
|
||||
|
||||
expect(await wsOpenPromise).toMatchObject({
|
||||
event: WS_OPEN_EVENT_PARTIAL,
|
||||
wsKey: WS_KEY_MAP.usdcPerpPublic,
|
||||
});
|
||||
});
|
||||
|
||||
it('should subscribe to public trade events', async () => {
|
||||
const wsResponsePromise = waitForSocketEvent(wsClient, 'response');
|
||||
const wsUpdatePromise = waitForSocketEvent(wsClient, 'update');
|
||||
|
||||
const topic = 'orderBook_200.100ms.BTCPERP';
|
||||
wsClient.subscribe(topic);
|
||||
|
||||
try {
|
||||
expect(await wsResponsePromise).toMatchObject({
|
||||
success: true,
|
||||
ret_msg: '',
|
||||
request: {
|
||||
op: 'subscribe',
|
||||
args: [topic],
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
// sub failed
|
||||
expect(e).toBeFalsy();
|
||||
}
|
||||
|
||||
try {
|
||||
expect(await wsUpdatePromise).toMatchObject({
|
||||
crossSeq: expect.any(String),
|
||||
data: { orderBook: expect.any(Array) },
|
||||
timestampE6: expect.any(String),
|
||||
topic: topic,
|
||||
type: 'snapshot',
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('usdc perp ws public error: ', e);
|
||||
// no data
|
||||
expect(e).toBeFalsy();
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user