cleaning around tests

This commit is contained in:
tiagosiebler
2022-09-15 19:06:35 +01:00
parent 27bd81593c
commit f61e79934d
10 changed files with 323 additions and 76 deletions

View File

@@ -1,6 +1,6 @@
import { RestClientOptions, WS_KEY_MAP } from '../util'; import { RestClientOptions, WS_KEY_MAP } from '../util';
export type APIMarket = 'inverse' | 'linear' | 'spot'; //| 'v3'; export type APIMarket = 'inverse' | 'linear' | 'spot' | 'spotV3'; //| 'v3';
// Same as inverse futures // Same as inverse futures
export type WsPublicInverseTopic = export type WsPublicInverseTopic =

View File

@@ -1,4 +1,4 @@
import { WsKey } from '../types'; import { APIMarket, WsKey } from '../types';
interface NetworkMapV3 { interface NetworkMapV3 {
livenet: string; livenet: string;
@@ -10,42 +10,52 @@ interface NetworkMapV3 {
type PublicPrivateNetwork = 'public' | 'private'; type PublicPrivateNetwork = 'public' | 'private';
export const WS_BASE_URL_MAP: Record< export const WS_BASE_URL_MAP: Record<
string, APIMarket,
Record<PublicPrivateNetwork, NetworkMapV3> Record<PublicPrivateNetwork, NetworkMapV3>
> = { > = {
inverse: { inverse: {
private: { public: {
livenet: 'wss://stream.bybit.com/realtime', livenet: 'wss://stream.bybit.com/realtime',
testnet: 'wss://stream-testnet.bybit.com/realtime', testnet: 'wss://stream-testnet.bybit.com/realtime',
}, },
public: { private: {
livenet: 'wss://stream.bybit.com/realtime', livenet: 'wss://stream.bybit.com/realtime',
testnet: 'wss://stream-testnet.bybit.com/realtime', testnet: 'wss://stream-testnet.bybit.com/realtime',
}, },
}, },
linear: { linear: {
private: {
livenet: 'wss://stream.bybit.com/realtime_private',
livenet2: 'wss://stream.bytick.com/realtime_private',
testnet: 'wss://stream-testnet.bybit.com/realtime_private',
},
public: { public: {
livenet: 'wss://stream.bybit.com/realtime_public', livenet: 'wss://stream.bybit.com/realtime_public',
livenet2: 'wss://stream.bytick.com/realtime_public', livenet2: 'wss://stream.bytick.com/realtime_public',
testnet: 'wss://stream-testnet.bybit.com/realtime_public', testnet: 'wss://stream-testnet.bybit.com/realtime_public',
}, },
private: {
livenet: 'wss://stream.bybit.com/realtime_private',
livenet2: 'wss://stream.bytick.com/realtime_private',
testnet: 'wss://stream-testnet.bybit.com/realtime_private',
},
}, },
spot: { spot: {
private: {
livenet: 'wss://stream.bybit.com/spot/ws',
testnet: 'wss://stream-testnet.bybit.com/spot/ws',
},
public: { public: {
livenet: 'wss://stream.bybit.com/spot/quote/ws/v1', livenet: 'wss://stream.bybit.com/spot/quote/ws/v1',
livenet2: 'wss://stream.bybit.com/spot/quote/ws/v2', livenet2: 'wss://stream.bybit.com/spot/quote/ws/v2',
testnet: 'wss://stream-testnet.bybit.com/spot/quote/ws/v1', testnet: 'wss://stream-testnet.bybit.com/spot/quote/ws/v1',
testnet2: 'wss://stream-testnet.bybit.com/spot/quote/ws/v2', testnet2: 'wss://stream-testnet.bybit.com/spot/quote/ws/v2',
}, },
private: {
livenet: 'wss://stream.bybit.com/spot/ws',
testnet: 'wss://stream-testnet.bybit.com/spot/ws',
},
},
spotV3: {
public: {
livenet: 'wss://stream.bybit.com/spot/public/v3',
testnet: 'wss://stream-testnet.bybit.com/spot/public/v3',
},
private: {
livenet: 'wss://stream.bybit.com/spot/private/v3',
testnet: 'wss://stream-testnet.bybit.com/spot/private/v3',
},
}, },
}; };
@@ -55,6 +65,8 @@ export const WS_KEY_MAP = {
linearPublic: 'linearPublic', linearPublic: 'linearPublic',
spotPrivate: 'spotPrivate', spotPrivate: 'spotPrivate',
spotPublic: 'spotPublic', spotPublic: 'spotPublic',
spotV3Private: 'spotV3Private',
spotV3Public: 'spotV3Public',
} as const; } as const;
export const PUBLIC_WS_KEYS = [ export const PUBLIC_WS_KEYS = [
@@ -77,7 +89,10 @@ export function getLinearWsKeyForTopic(topic: string): WsKey {
return WS_KEY_MAP.linearPublic; return WS_KEY_MAP.linearPublic;
} }
export function getSpotWsKeyForTopic(topic: string): WsKey { export function getSpotWsKeyForTopic(
topic: string,
apiVersion: 'v1' | 'v3'
): WsKey {
const privateTopics = [ const privateTopics = [
'position', 'position',
'execution', 'execution',
@@ -88,6 +103,13 @@ export function getSpotWsKeyForTopic(topic: string): WsKey {
'ticketInfo', 'ticketInfo',
]; ];
if (apiVersion === 'v3') {
if (privateTopics.includes(topic)) {
return WS_KEY_MAP.spotV3Private;
}
return WS_KEY_MAP.spotV3Public;
}
if (privateTopics.includes(topic)) { if (privateTopics.includes(topic)) {
return WS_KEY_MAP.spotPrivate; return WS_KEY_MAP.spotPrivate;
} }

View File

@@ -136,6 +136,17 @@ export class WebsocketClient extends EventEmitter {
this.connectPublic(); this.connectPublic();
break; break;
} }
case 'spotV3': {
this.restClient = new SpotClientV3(
undefined,
undefined,
!this.isTestnet(),
this.options.restOptions,
this.options.requestOptions
);
this.connectPublic();
break;
}
// if (this.isV3()) { // if (this.isV3()) {
// this.restClient = new SpotClientV3( // this.restClient = new SpotClientV3(
// undefined, // undefined,
@@ -175,59 +186,6 @@ export class WebsocketClient extends EventEmitter {
// return this.options.market === 'v3'; // return this.options.market === 'v3';
// } // }
/**
* Add topic/topics to WS subscription list
*/
public subscribe(wsTopics: WsTopic[] | WsTopic) {
const topics = Array.isArray(wsTopics) ? wsTopics : [wsTopics];
topics.forEach((topic) =>
this.wsStore.addTopic(this.getWsKeyForTopic(topic), topic)
);
// attempt to send subscription topic per websocket
this.wsStore.getKeys().forEach((wsKey: WsKey) => {
// if connected, send subscription request
if (
this.wsStore.isConnectionState(wsKey, WsConnectionStateEnum.CONNECTED)
) {
return this.requestSubscribeTopics(wsKey, topics);
}
// start connection process if it hasn't yet begun. Topics are automatically subscribed to on-connect
if (
!this.wsStore.isConnectionState(
wsKey,
WsConnectionStateEnum.CONNECTING
) &&
!this.wsStore.isConnectionState(
wsKey,
WsConnectionStateEnum.RECONNECTING
)
) {
return this.connect(wsKey);
}
});
}
/**
* Remove topic/topics from WS subscription list
*/
public unsubscribe(wsTopics: WsTopic[] | WsTopic) {
const topics = Array.isArray(wsTopics) ? wsTopics : [wsTopics];
topics.forEach((topic) =>
this.wsStore.deleteTopic(this.getWsKeyForTopic(topic), topic)
);
this.wsStore.getKeys().forEach((wsKey: WsKey) => {
// unsubscribe request only necessary if active connection exists
if (
this.wsStore.isConnectionState(wsKey, WsConnectionStateEnum.CONNECTED)
) {
this.requestUnsubscribeTopics(wsKey, topics);
}
});
}
public close(wsKey: WsKey) { public close(wsKey: WsKey) {
this.logger.info('Closing connection', { ...loggerCategory, wsKey }); this.logger.info('Closing connection', { ...loggerCategory, wsKey });
this.setWsState(wsKey, WsConnectionStateEnum.CLOSING); this.setWsState(wsKey, WsConnectionStateEnum.CLOSING);
@@ -263,6 +221,12 @@ export class WebsocketClient extends EventEmitter {
this.connect(WS_KEY_MAP.spotPrivate), this.connect(WS_KEY_MAP.spotPrivate),
]; ];
} }
case 'spotV3': {
return [
this.connect(WS_KEY_MAP.spotV3Public),
this.connect(WS_KEY_MAP.spotV3Private),
];
}
default: { default: {
throw neverGuard(this.options.market, `connectAll(): Unhandled market`); throw neverGuard(this.options.market, `connectAll(): Unhandled market`);
} }
@@ -280,6 +244,9 @@ export class WebsocketClient extends EventEmitter {
case 'spot': { case 'spot': {
return this.connect(WS_KEY_MAP.spotPublic); return this.connect(WS_KEY_MAP.spotPublic);
} }
case 'spotV3': {
return this.connect(WS_KEY_MAP.spotV3Public);
}
default: { default: {
throw neverGuard( throw neverGuard(
this.options.market, this.options.market,
@@ -300,6 +267,9 @@ export class WebsocketClient extends EventEmitter {
case 'spot': { case 'spot': {
return this.connect(WS_KEY_MAP.spotPrivate); return this.connect(WS_KEY_MAP.spotPrivate);
} }
case 'spotV3': {
return this.connect(WS_KEY_MAP.spotV3Private);
}
default: { default: {
throw neverGuard( throw neverGuard(
this.options.market, this.options.market,
@@ -503,7 +473,7 @@ export class WebsocketClient extends EventEmitter {
this.tryWsSend(wsKey, wsMessage); this.tryWsSend(wsKey, wsMessage);
} }
private tryWsSend(wsKey: WsKey, wsMessage: string) { public tryWsSend(wsKey: WsKey, wsMessage: string) {
try { try {
this.logger.silly(`Sending upstream ws message: `, { this.logger.silly(`Sending upstream ws message: `, {
...loggerCategory, ...loggerCategory,
@@ -666,7 +636,13 @@ export class WebsocketClient extends EventEmitter {
return WS_BASE_URL_MAP.spot.public[networkKey]; return WS_BASE_URL_MAP.spot.public[networkKey];
} }
case WS_KEY_MAP.spotPrivate: { case WS_KEY_MAP.spotPrivate: {
return WS_BASE_URL_MAP.linear.private[networkKey]; return WS_BASE_URL_MAP.spot.private[networkKey];
}
case WS_KEY_MAP.spotV3Public: {
return WS_BASE_URL_MAP.spot.public[networkKey];
}
case WS_KEY_MAP.spotV3Private: {
return WS_BASE_URL_MAP.spot.private[networkKey];
} }
case WS_KEY_MAP.inverse: { case WS_KEY_MAP.inverse: {
// private and public are on the same WS connection // private and public are on the same WS connection
@@ -691,7 +667,10 @@ export class WebsocketClient extends EventEmitter {
return getLinearWsKeyForTopic(topic); return getLinearWsKeyForTopic(topic);
} }
case 'spot': { case 'spot': {
return getSpotWsKeyForTopic(topic); return getSpotWsKeyForTopic(topic, 'v1');
}
case 'spotV3': {
return getSpotWsKeyForTopic(topic, 'v3');
} }
default: { default: {
throw neverGuard( throw neverGuard(
@@ -708,6 +687,59 @@ export class WebsocketClient extends EventEmitter {
); );
} }
/**
* Add topic/topics to WS subscription list
*/
public subscribe(wsTopics: WsTopic[] | WsTopic) {
const topics = Array.isArray(wsTopics) ? wsTopics : [wsTopics];
topics.forEach((topic) =>
this.wsStore.addTopic(this.getWsKeyForTopic(topic), topic)
);
// attempt to send subscription topic per websocket
this.wsStore.getKeys().forEach((wsKey: WsKey) => {
// if connected, send subscription request
if (
this.wsStore.isConnectionState(wsKey, WsConnectionStateEnum.CONNECTED)
) {
return this.requestSubscribeTopics(wsKey, topics);
}
// start connection process if it hasn't yet begun. Topics are automatically subscribed to on-connect
if (
!this.wsStore.isConnectionState(
wsKey,
WsConnectionStateEnum.CONNECTING
) &&
!this.wsStore.isConnectionState(
wsKey,
WsConnectionStateEnum.RECONNECTING
)
) {
return this.connect(wsKey);
}
});
}
/**
* Remove topic/topics from WS subscription list
*/
public unsubscribe(wsTopics: WsTopic[] | WsTopic) {
const topics = Array.isArray(wsTopics) ? wsTopics : [wsTopics];
topics.forEach((topic) =>
this.wsStore.deleteTopic(this.getWsKeyForTopic(topic), topic)
);
this.wsStore.getKeys().forEach((wsKey: WsKey) => {
// unsubscribe request only necessary if active connection exists
if (
this.wsStore.isConnectionState(wsKey, WsConnectionStateEnum.CONNECTED)
) {
this.requestUnsubscribeTopics(wsKey, topics);
}
});
}
// TODO: persistance for subbed topics. Look at ftx-api implementation. // TODO: persistance for subbed topics. Look at ftx-api implementation.
public subscribePublicSpotTrades(symbol: string, binary?: boolean) { public subscribePublicSpotTrades(symbol: string, binary?: boolean) {
if (!this.isSpot()) { if (!this.isSpot()) {

View File

@@ -27,7 +27,7 @@ describe('Public Inverse Perps Websocket Client', () => {
wsClient.closeAll(); wsClient.closeAll();
}); });
it('should open a private ws connection', async () => { it('should open a public ws connection', async () => {
const wsOpenPromise = waitForSocketEvent(wsClient, 'open'); const wsOpenPromise = waitForSocketEvent(wsClient, 'open');
expect(wsOpenPromise).resolves.toMatchObject({ expect(wsOpenPromise).resolves.toMatchObject({

View File

@@ -9,7 +9,7 @@ import {
WS_OPEN_EVENT_PARTIAL, WS_OPEN_EVENT_PARTIAL,
} from '../ws.util'; } from '../ws.util';
describe('Private Linear Websocket Client', () => { describe('Private Linear Perps Websocket Client', () => {
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

@@ -1,5 +1,4 @@
import { import {
LinearClient,
WebsocketClient, WebsocketClient,
WSClientConfigurableOptions, WSClientConfigurableOptions,
WS_KEY_MAP, WS_KEY_MAP,
@@ -10,7 +9,7 @@ import {
WS_OPEN_EVENT_PARTIAL, WS_OPEN_EVENT_PARTIAL,
} from '../ws.util'; } from '../ws.util';
describe('Public Linear Websocket Client', () => { describe('Public Linear Perps Websocket Client', () => {
let wsClient: WebsocketClient; let wsClient: WebsocketClient;
const wsClientOptions: WSClientConfigurableOptions = { const wsClientOptions: WSClientConfigurableOptions = {
@@ -26,7 +25,7 @@ describe('Public Linear Websocket Client', () => {
wsClient.closeAll(); wsClient.closeAll();
}); });
it('should open a private ws connection', async () => { it('should open a public ws connection', async () => {
const wsOpenPromise = waitForSocketEvent(wsClient, 'open'); const wsOpenPromise = waitForSocketEvent(wsClient, 'open');
expect(wsOpenPromise).resolves.toMatchObject({ expect(wsOpenPromise).resolves.toMatchObject({

View File

@@ -0,0 +1,58 @@
import {
WebsocketClient,
WSClientConfigurableOptions,
WS_KEY_MAP,
} from '../../src';
import {
logAllEvents,
promiseSleep,
silentLogger,
waitForSocketEvent,
WS_OPEN_EVENT_PARTIAL,
} from '../ws.util';
describe('Private Spot V1 Websocket Client', () => {
let wsClient: WebsocketClient;
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 wsClientOptions: WSClientConfigurableOptions = {
market: 'spot',
key: API_KEY,
secret: API_SECRET,
};
beforeAll(() => {
wsClient = new WebsocketClient(wsClientOptions, silentLogger);
logAllEvents(wsClient);
});
afterAll(() => {
wsClient.closeAll();
});
// TODO: how to detect if auth failed for the v1 spot ws
it('should open a private ws connection', async () => {
const wsOpenPromise = waitForSocketEvent(wsClient, 'open');
// const wsUpdatePromise = waitForSocketEvent(wsClient, 'update');
wsClient.connectPrivate();
expect(wsOpenPromise).resolves.toMatchObject({
event: WS_OPEN_EVENT_PARTIAL,
wsKey: WS_KEY_MAP.spotPrivate,
});
// expect(wsUpdatePromise).resolves.toMatchObject({
// topic: 'wsTopic',
// data: expect.any(Array),
// });
await Promise.all([wsOpenPromise]);
// await Promise.all([wsUpdatePromise]);
// await promiseSleep(4000);
});
});

View File

@@ -0,0 +1,64 @@
import {
WebsocketClient,
WSClientConfigurableOptions,
WS_KEY_MAP,
} from '../../src';
import {
logAllEvents,
silentLogger,
waitForSocketEvent,
WS_OPEN_EVENT_PARTIAL,
} from '../ws.util';
describe('Public Spot V1 Websocket Client', () => {
let wsClient: WebsocketClient;
const wsClientOptions: WSClientConfigurableOptions = {
market: 'spot',
};
beforeAll(() => {
wsClient = new WebsocketClient(wsClientOptions, silentLogger);
wsClient.connectPublic();
// logAllEvents(wsClient);
});
afterAll(() => {
wsClient.closeAll();
});
it('should open a public ws connection', async () => {
const wsOpenPromise = waitForSocketEvent(wsClient, 'open');
expect(wsOpenPromise).resolves.toMatchObject({
event: WS_OPEN_EVENT_PARTIAL,
wsKey: WS_KEY_MAP.spotPublic,
});
await Promise.all([wsOpenPromise]);
});
it('should subscribe to public orderbook events', async () => {
const wsUpdatePromise = waitForSocketEvent(wsClient, 'update');
const symbol = 'BTCUSDT';
expect(wsUpdatePromise).resolves.toMatchObject({
symbol: symbol,
symbolName: symbol,
topic: 'diffDepth',
params: {
realtimeInterval: '24h',
binary: 'false',
},
data: expect.any(Array),
});
wsClient.subscribePublicSpotOrderbook(symbol, 'delta');
try {
await wsUpdatePromise;
} catch (e) {
console.error(`Wait for spot v1 orderbook event exception: `, e);
}
});
});

View File

@@ -0,0 +1,72 @@
import {
WebsocketClient,
WSClientConfigurableOptions,
WS_KEY_MAP,
} from '../../src';
import {
logAllEvents,
silentLogger,
waitForSocketEvent,
WS_OPEN_EVENT_PARTIAL,
} from '../ws.util';
describe('Public Spot V3 Websocket Client', () => {
let wsClient: WebsocketClient;
const wsClientOptions: WSClientConfigurableOptions = {
market: 'spotV3',
};
beforeAll(() => {
wsClient = new WebsocketClient(wsClientOptions, silentLogger);
wsClient.connectPublic();
// logAllEvents(wsClient);
});
afterAll(() => {
wsClient.closeAll();
});
it('should open a public ws connection', async () => {
const wsOpenPromise = waitForSocketEvent(wsClient, 'open');
expect(wsOpenPromise).resolves.toMatchObject({
event: WS_OPEN_EVENT_PARTIAL,
wsKey: WS_KEY_MAP.spotV3Public,
});
await Promise.all([wsOpenPromise]);
});
it('should subscribe to public orderbook events', async () => {
const wsResponsePromise = waitForSocketEvent(wsClient, 'response');
const wsUpdatePromise = waitForSocketEvent(wsClient, 'update');
const wsTopic = 'orderbook.40.BTCUSDT';
expect(wsResponsePromise).resolves.toMatchObject({
request: {
args: [wsTopic],
op: 'subscribe',
},
success: true,
});
expect(wsUpdatePromise).resolves.toStrictEqual('');
wsClient.subscribe(wsTopic);
try {
await wsResponsePromise;
} catch (e) {
console.error(
`Wait for "${wsTopic}" subscription response exception: `,
e
);
}
try {
await wsUpdatePromise;
} catch (e) {
console.error(`Wait for "${wsTopic}" event exception: `, e);
}
});
});