add e2e tests for v5 REST client. Fix competing tests.

This commit is contained in:
tiagosiebler
2023-02-20 16:19:56 +00:00
parent 9e2e105961
commit eeb0d63f0d
10 changed files with 498 additions and 27 deletions

View File

@@ -13,7 +13,7 @@ Node.js SDK for the Bybit APIs and WebSockets:
- Complete integration with all Bybit APIs. - Complete integration with all Bybit APIs.
- TypeScript support (with type declarations for most API requests & responses). - TypeScript support (with type declarations for most API requests & responses).
- Over 300 end-to-end tests making real API calls & WebSocket connections, validating any changes before they reach npm. - Over 450 end-to-end tests making real API calls & WebSocket connections, validating any changes before they reach npm.
- Robust WebSocket integration with configurable connection heartbeats & automatic reconnect then resubscribe workflows. - Robust WebSocket integration with configurable connection heartbeats & automatic reconnect then resubscribe workflows.
- Browser support (via webpack bundle - see "Browser Usage" below). - Browser support (via webpack bundle - see "Browser Usage" below).

View File

@@ -44,9 +44,20 @@ export const API_ERROR_CODE = {
POSITION_MODE_NOT_MODIFIED: 30083, POSITION_MODE_NOT_MODIFIED: 30083,
ISOLATED_NOT_MODIFIED: 30084, ISOLATED_NOT_MODIFIED: 30084,
RISK_LIMIT_NOT_EXISTS: 30090, RISK_LIMIT_NOT_EXISTS: 30090,
SUB_USER_ALREADY_EXISTS: 31005,
LEVERAGE_NOT_MODIFIED: 34036, LEVERAGE_NOT_MODIFIED: 34036,
SAME_SLTP_MODE: 37002, SAME_SLTP_MODE: 37002,
COPY_TRADE_NOT_OPEN_ORDER: 39426, COPY_TRADE_NOT_OPEN_ORDER: 39426,
V5_ORDER_NOT_FOUND: 110001,
V5_INSUFFICIENT_BALANCE: 110007,
V5_API_KEY_PERMISSION_DENIED: 10005,
V5_CROSS_ISOLATED_MARGIN_NOT_CHANGED: 110026,
V5_LEVERAGE_NOT_CHANGED: 110043,
V5_MARGIN_MODE_NOT_CHANGED: 110073,
V5_TPSL_NOT_CHANGED: 10001,
V5_RISK_ID_NOT_CHANGED: 10001,
V5_AUTO_ADD_MARGIN_NOT_CHANGED: 10001,
V5_TPSL_ERROR_NO_POSITION: 10001,
QTY_EXCEEDS_MAX_LIMIT: 130006, QTY_EXCEEDS_MAX_LIMIT: 130006,
ORDER_NOT_FOUND_OR_TOO_LATE_LINEAR: 130010, ORDER_NOT_FOUND_OR_TOO_LATE_LINEAR: 130010,
ORDER_COST_NOT_AVAILABLE: 130021, ORDER_COST_NOT_AVAILABLE: 130021,
@@ -56,6 +67,7 @@ export const API_ERROR_CODE = {
AUTO_ADD_MARGIN_NOT_MODIFIED: 130060, AUTO_ADD_MARGIN_NOT_MODIFIED: 130060,
INSUFFICIENT_BALANCE_FOR_ORDER_COST_LINEAR: 130080, INSUFFICIENT_BALANCE_FOR_ORDER_COST_LINEAR: 130080,
SAME_SLTP_MODE_LINEAR: 130150, SAME_SLTP_MODE_LINEAR: 130150,
TRANSFER_ID_EXISTS: 131214,
RISK_ID_NOT_MODIFIED: 134026, RISK_ID_NOT_MODIFIED: 134026,
CONTRACT_ORDER_NOT_EXISTS: 140001, CONTRACT_ORDER_NOT_EXISTS: 140001,
CONTRACT_INSUFFICIENT_BALANCE: 140007, CONTRACT_INSUFFICIENT_BALANCE: 140007,
@@ -63,7 +75,10 @@ export const API_ERROR_CODE = {
CONTRACT_MARGIN_MODE_NOT_MODIFIED: 140026, CONTRACT_MARGIN_MODE_NOT_MODIFIED: 140026,
CONTRACT_RISK_LIMIT_INFO_NOT_EXISTS: 140031, CONTRACT_RISK_LIMIT_INFO_NOT_EXISTS: 140031,
CONTRACT_SET_LEVERAGE_NOT_MODIFIED: 140043, CONTRACT_SET_LEVERAGE_NOT_MODIFIED: 140043,
SUB_USER_NOT_FOUND: 141009,
SPOT_LEVERAGE_TOKEN_ORDER_NOT_FOUND: 175007, SPOT_LEVERAGE_TOKEN_ORDER_NOT_FOUND: 175007,
SPOT_MARGIN_NOT_ENABLED: 176008,
SPOT_MARGIN_QUESTIONNAIRE_NOT_SUBMIT: 176037,
/** E.g. USDC Options trading, trying to access a symbol that is no longer active */ /** E.g. USDC Options trading, trying to access a symbol that is no longer active */
CONTRACT_NAME_NOT_EXIST: 3100111, CONTRACT_NAME_NOT_EXIST: 3100111,
ORDER_NOT_EXIST: 3100136, ORDER_NOT_EXIST: 3100136,

View File

@@ -489,6 +489,8 @@ export class RestClientV5 extends BaseRestClient {
/** /**
* Query real-time position data, such as position size, cumulative realizedPNL. * Query real-time position data, such as position size, cumulative realizedPNL.
* *
* 0: cross margin. 1: isolated margin
*
* Unified account covers: Linear contract / Options * Unified account covers: Linear contract / Options
* *
* Normal account covers: USDT perpetual / Inverse perpetual / Inverse futures * Normal account covers: USDT perpetual / Inverse perpetual / Inverse futures
@@ -516,6 +518,7 @@ export class RestClientV5 extends BaseRestClient {
/** /**
* Select cross margin mode or isolated margin mode. * Select cross margin mode or isolated margin mode.
* 0: cross margin. 1: isolated margin
* *
* Covers: USDT perpetual (Normal account) / Inverse contract (Normal account). * Covers: USDT perpetual (Normal account) / Inverse contract (Normal account).
* *

View File

@@ -71,18 +71,6 @@ export interface GetUniversalTransferRecordsParamsV5 {
cursor?: string; cursor?: string;
} }
export interface UniversalTransferRecordV5 {
transferId: string;
coin: string;
amount: string;
fromMemberId: string;
toMemberId: string;
fromAccountType: AccountTypeV5;
toAccountType: AccountTypeV5;
timestamp: string;
status: string;
}
export interface GetAllowedDepositCoinInfoParamsV5 { export interface GetAllowedDepositCoinInfoParamsV5 {
coin?: string; coin?: string;
chain?: string; chain?: string;
@@ -123,7 +111,6 @@ export interface WithdrawParamsV5 {
address: string; address: string;
tag?: string; tag?: string;
amount: string; amount: string;
timestamp: number;
forceChain?: number; forceChain?: number;
accountType?: 'SPOT' | 'FUND'; accountType?: 'SPOT' | 'FUND';
} }

View File

@@ -1,4 +1,10 @@
import { CategoryV5, ExecTypeV5, PositionIdx, TPSLModeV5 } from '../v5-shared'; import {
CategoryV5,
ExecTypeV5,
OrderTriggerByV5,
PositionIdx,
TPSLModeV5,
} from '../v5-shared';
export interface PositionInfoParamsV5 { export interface PositionInfoParamsV5 {
category: CategoryV5; category: CategoryV5;
@@ -50,8 +56,8 @@ export interface SetTradingStopParamsV5 {
takeProfit?: string; takeProfit?: string;
stopLoss?: string; stopLoss?: string;
trailingStop?: string; trailingStop?: string;
tpTriggerBy?: string; tpTriggerBy?: OrderTriggerByV5;
slTriggerBy?: string; slTriggerBy?: OrderTriggerByV5;
activePrice?: string; activePrice?: string;
tpSize?: string; tpSize?: string;
slSize?: string; slSize?: string;

View File

@@ -79,6 +79,18 @@ export interface InternalTransferRecordV5 {
status: string; status: string;
} }
export interface UniversalTransferRecordV5 {
transferId: string;
coin: string;
amount: string;
fromMemberId: string;
toMemberId: string;
fromAccountType: AccountTypeV5;
toAccountType: AccountTypeV5;
timestamp: string;
status: string;
}
export interface AllowedDepositCoinInfoV5 { export interface AllowedDepositCoinInfoV5 {
coin: string; coin: string;
chain: string; chain: string;

View File

@@ -89,8 +89,8 @@ describe('Private Contract REST API POST Endpoints', () => {
await api.setMarginSwitch({ await api.setMarginSwitch({
symbol, symbol,
tradeMode: 1, tradeMode: 1,
buyLeverage: '5', buyLeverage: '10',
sellLeverage: '5', sellLeverage: '10',
}) })
).toMatchObject({ ).toMatchObject({
retCode: API_ERROR_CODE.CONTRACT_MARGIN_MODE_NOT_MODIFIED, retCode: API_ERROR_CODE.CONTRACT_MARGIN_MODE_NOT_MODIFIED,
@@ -116,7 +116,7 @@ describe('Private Contract REST API POST Endpoints', () => {
}); });
it('setLeverage()', async () => { it('setLeverage()', async () => {
expect(await api.setLeverage(symbol, '5', '5')).toMatchObject({ expect(await api.setLeverage(symbol, '10', '10')).toMatchObject({
retCode: API_ERROR_CODE.CONTRACT_SET_LEVERAGE_NOT_MODIFIED, retCode: API_ERROR_CODE.CONTRACT_SET_LEVERAGE_NOT_MODIFIED,
}); });
}); });

View File

@@ -138,8 +138,8 @@ describe('Private Linear REST API POST Endpoints', () => {
await api.setMarginSwitch({ await api.setMarginSwitch({
symbol, symbol,
is_isolated: true, is_isolated: true,
buy_leverage: 5, buy_leverage: 10,
sell_leverage: 5, sell_leverage: 10,
}) })
).toMatchObject({ ).toMatchObject({
ret_code: API_ERROR_CODE.ISOLATED_NOT_MODIFIED_LINEAR, ret_code: API_ERROR_CODE.ISOLATED_NOT_MODIFIED_LINEAR,
@@ -186,8 +186,8 @@ describe('Private Linear REST API POST Endpoints', () => {
expect( expect(
await api.setUserLeverage({ await api.setUserLeverage({
symbol, symbol,
buy_leverage: 5, buy_leverage: 10,
sell_leverage: 5, sell_leverage: 10,
}) })
).toMatchObject({ ).toMatchObject({
ret_code: API_ERROR_CODE.LEVERAGE_NOT_MODIFIED, ret_code: API_ERROR_CODE.LEVERAGE_NOT_MODIFIED,
@@ -206,7 +206,7 @@ describe('Private Linear REST API POST Endpoints', () => {
}); });
}); });
it('setRiskLimit()', async () => { it.skip('setRiskLimit()', async () => {
expect( expect(
await api.setRiskLimit({ await api.setRiskLimit({
symbol, symbol,

View File

@@ -1,7 +1,7 @@
import { API_ERROR_CODE, RestClientV5 } from '../../src'; import { API_ERROR_CODE, RestClientV5 } from '../../src';
import { SUCCESS_MSG_REGEX, successResponseObjectV3 } from '../response.util'; import { successResponseObjectV3 } from '../response.util';
describe('Private V5 REST API Endpoints', () => { describe('Private READ V5 REST API Endpoints', () => {
const API_KEY = process.env.API_KEY_COM; const API_KEY = process.env.API_KEY_COM;
const API_SECRET = process.env.API_SECRET_COM; const API_SECRET = process.env.API_SECRET_COM;

View File

@@ -0,0 +1,448 @@
import {
API_ERROR_CODE,
OrderSideV5,
OrderTypeV5,
RestClientV5,
} from '../../src';
import { successResponseObjectV3 } from '../response.util';
describe('Private WRITE V5 REST API 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 RestClientV5({
key: API_KEY,
secret: API_SECRET,
testnet: false,
});
const settleCoin = 'USDT';
const linearSymbol = 'BTCUSDT';
const orderType: OrderTypeV5 = 'Market';
const orderSide: OrderSideV5 = 'Buy';
const fakeOrderId = 'fakeOrderId';
const fakeTransferId = '42c0cfb0-6bca-c242-bc76-4e6df6cbcb16';
describe('misc endpoints', () => {
it('fetchServerTime()', async () => {
expect(await api.fetchServerTime()).toEqual(expect.any(Number));
});
});
describe('Trade APIs', () => {
it('submitOrder()', async () => {
expect(
await api.submitOrder({
category: 'linear',
symbol: linearSymbol,
orderType: orderType,
side: orderSide,
qty: '1',
positionIdx: 1,
})
).toMatchObject({
// ...successResponseObjectV3(),
// retMsg: '',
retCode: API_ERROR_CODE.V5_INSUFFICIENT_BALANCE,
});
});
it('amendOrder()', async () => {
expect(
await api.amendOrder({
category: 'linear',
symbol: linearSymbol,
qty: '2',
orderId: fakeOrderId,
})
).toMatchObject({
// ...successResponseObjectV3(),
// retMsg: '',
retCode: API_ERROR_CODE.V5_ORDER_NOT_FOUND,
});
});
it('cancelOrder()', async () => {
expect(
await api.cancelOrder({
category: 'linear',
symbol: linearSymbol,
orderId: fakeOrderId,
})
).toMatchObject({
// ...successResponseObjectV3(),
// retMsg: '',
retCode: API_ERROR_CODE.V5_ORDER_NOT_FOUND,
});
});
it('cancelAllOrders()', async () => {
expect(
await api.cancelAllOrders({
category: 'linear',
settleCoin: settleCoin,
})
).toMatchObject({
...successResponseObjectV3(),
});
});
it('batchSubmitOrders()', async () => {
expect(
await api.batchSubmitOrders('option', [
{
orderLinkId: 'customOrderId1',
orderType: orderType,
qty: '1',
side: orderSide,
symbol: linearSymbol,
},
{
orderLinkId: 'customOrderId2',
orderType: orderType,
qty: '2',
side: orderSide,
symbol: linearSymbol,
},
])
).toMatchObject({
...successResponseObjectV3(),
});
});
it('batchAmendOrders()', async () => {
expect(
await api.batchAmendOrders('option', [
{
orderLinkId: 'customOrderId1',
qty: '3',
symbol: linearSymbol,
},
{
orderLinkId: 'customOrderId2',
qty: '4',
symbol: linearSymbol,
},
])
).toMatchObject({
...successResponseObjectV3(),
});
});
it('batchCancelOrders()', async () => {
expect(
await api.batchCancelOrders('option', [
{
orderLinkId: 'customOrderId1',
symbol: linearSymbol,
},
{
orderLinkId: 'customOrderId2',
symbol: linearSymbol,
},
])
).toMatchObject({
...successResponseObjectV3(),
});
});
it('setDisconnectCancelAllWindow()', async () => {
expect(await api.setDisconnectCancelAllWindow('option', 5)).toMatchObject(
{
// ...successResponseObjectV3(),
// retMsg: '',
retCode: API_ERROR_CODE.V5_API_KEY_PERMISSION_DENIED,
}
);
});
});
describe('Position APIs', () => {
it('setLeverage()', async () => {
expect(
await api.setLeverage({
category: 'linear',
buyLeverage: '10',
sellLeverage: '10',
symbol: linearSymbol,
})
).toMatchObject({
// ...successResponseObjectV3(),
// retMsg: '',
retCode: API_ERROR_CODE.V5_LEVERAGE_NOT_CHANGED,
});
});
it('switchIsolatedMargin()', async () => {
expect(
await api.switchIsolatedMargin({
category: 'linear',
buyLeverage: '10',
sellLeverage: '10',
symbol: linearSymbol,
// isolated
tradeMode: 1,
})
).toMatchObject({
// ...successResponseObjectV3(),
// retMsg: '',
retCode: API_ERROR_CODE.V5_CROSS_ISOLATED_MARGIN_NOT_CHANGED,
});
});
it('setTPSLMode()', async () => {
expect(
await api.setTPSLMode({
category: 'linear',
symbol: linearSymbol,
tpSlMode: 'Full',
})
).toMatchObject({
// ...successResponseObjectV3(),
// retMsg: '',
retCode: API_ERROR_CODE.V5_TPSL_NOT_CHANGED,
});
});
it('switchPositionMode()', async () => {
expect(
await api.switchPositionMode({
category: 'linear',
// both sides
mode: 3,
coin: settleCoin,
})
).toMatchObject({
...successResponseObjectV3(),
});
});
it('setRiskLimit()', async () => {
expect(
await api.setRiskLimit({
category: 'linear',
positionIdx: 1,
riskId: 1,
symbol: linearSymbol,
})
).toMatchObject({
// ...successResponseObjectV3(),
// retMsg: '',
retCode: API_ERROR_CODE.V5_RISK_ID_NOT_CHANGED,
});
});
it('setTradingStop()', async () => {
expect(
await api.setTradingStop({
category: 'linear',
positionIdx: 1,
symbol: linearSymbol,
slSize: '100',
slTriggerBy: 'LastPrice',
stopLoss: '25000',
})
).toMatchObject({
// ...successResponseObjectV3(),
// retMsg: '',
retCode: API_ERROR_CODE.V5_TPSL_ERROR_NO_POSITION,
});
});
it('setAutoAddMargin()', async () => {
expect(
await api.setAutoAddMargin({
category: 'linear',
autoAddMargin: 0,
symbol: linearSymbol,
positionIdx: 0,
})
).toMatchObject({
// ...successResponseObjectV3(),
// retMsg: '',
retCode: API_ERROR_CODE.V5_AUTO_ADD_MARGIN_NOT_CHANGED,
});
});
});
describe('Account APIs', () => {
it('setMarginMode()', async () => {
expect(await api.setMarginMode('REGULAR_MARGIN')).toMatchObject({
// ...successResponseObjectV3(),
// retMsg: '',
retCode: API_ERROR_CODE.V5_MARGIN_MODE_NOT_CHANGED,
});
});
it('setMMP()', async () => {
expect(
await api.setMMP({
baseCoin: settleCoin,
deltaLimit: '1',
frozenPeriod: '1',
qtyLimit: '1',
window: '1',
})
).toMatchObject({
// ...successResponseObjectV3(),
// retMsg: '',
retCode: API_ERROR_CODE.INSTITION_MMP_PROFILE_NOT_FOUND,
});
});
it.skip('resetMMP()', async () => {
expect(await api.resetMMP(settleCoin)).toMatchObject({
...successResponseObjectV3(),
retMsg: '',
retCode: 3500715,
// Contacted bybit for info
// + "retMsg": "Parameter window cannot be empty.",
});
});
});
describe('Asset APIs', () => {
it('createInternalTransfer()', async () => {
expect(
await api.createInternalTransfer(
fakeTransferId,
settleCoin,
'100',
'SPOT',
'CONTRACT'
)
).toMatchObject({
// ...successResponseObjectV3(),
// retMsg: '',
retCode: API_ERROR_CODE.TRANSFER_ID_EXISTS,
});
});
it('enableUniversalTransferForSubUIDs()', async () => {
expect(await api.enableUniversalTransferForSubUIDs([])).toMatchObject({
...successResponseObjectV3(),
});
});
it('createUniversalTransfer()', async () => {
expect(
await api.createUniversalTransfer({
amount: '100',
coin: settleCoin,
fromAccountType: 'SPOT',
fromMemberId: 1,
toAccountType: 'CONTRACT',
toMemberId: 2,
transferId: fakeTransferId,
})
).toMatchObject({
// ...successResponseObjectV3(),
// retMsg: '',
retCode: API_ERROR_CODE.TRANSFER_ID_EXISTS,
});
});
it('submitWithdrawal()', async () => {
expect(
await api.submitWithdrawal({
address: '0x000000',
amount: '100',
chain: 'TRC20',
coin: settleCoin,
})
).toMatchObject({
// ...successResponseObjectV3(),
// retMsg: '',
retCode: API_ERROR_CODE.INCORRECT_API_KEY_PERMISSIONS,
});
});
it('cancelWithdrawal()', async () => {
expect(await api.cancelWithdrawal('fakeId')).toMatchObject({
// ...successResponseObjectV3(),
// retMsg: '',
retCode: API_ERROR_CODE.INCORRECT_API_KEY_PERMISSIONS,
});
});
});
describe('User APIs', () => {
it('createSubMember()', async () => {
expect(
await api.createSubMember({
memberType: 1,
username: 'sub1account',
switch: 1,
note: 'created via e2e test',
})
).toMatchObject({
// ...successResponseObjectV3(),
// retMsg: '',
retCode: API_ERROR_CODE.SUB_USER_ALREADY_EXISTS,
});
});
it('setSubUIDFrozenState()', async () => {
expect(await api.setSubUIDFrozenState(0, 1)).toMatchObject({
// ...successResponseObjectV3(),
// retMsg: '',
retCode: API_ERROR_CODE.SUB_USER_NOT_FOUND,
});
});
});
// Not currently working. In touch with bybit.
describe.skip('Spot Leverage Token APIs', () => {
it('purchaseSpotLeveragedToken()', async () => {
expect(
await api.purchaseSpotLeveragedToken({
ltAmount: '100',
ltCoin: 'EOS3L',
serialNo: 'purchase-001',
})
).toMatchObject({
// ...successResponseObjectV3(),
retCode: 0,
retMsg: '',
});
});
it('redeemSpotLeveragedToken()', async () => {
expect(
await api.redeemSpotLeveragedToken({
quantity: '100',
ltCoin: 'EOS3L',
serialNo: 'redeem-001',
})
).toMatchObject({
// ...successResponseObjectV3(),
retCode: 0,
retMsg: '',
});
});
});
describe('Spot Margin APIs', () => {
it('toggleSpotMarginTrade()', async () => {
expect(await api.toggleSpotMarginTrade('1')).toMatchObject({
// ...successResponseObjectV3(),
// retMsg: '',
retCode: API_ERROR_CODE.SPOT_MARGIN_QUESTIONNAIRE_NOT_SUBMIT,
});
});
it('setSpotMarginLeverage()', async () => {
expect(await api.setSpotMarginLeverage('2')).toMatchObject({
// ...successResponseObjectV3(),
// retMsg: '',
retCode: API_ERROR_CODE.SPOT_MARGIN_NOT_ENABLED,
});
});
});
});