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';
export type APIMarket = 'inverse' | 'linear' | 'spot'; //| 'v3';
export type APIMarket = 'inverse' | 'linear' | 'spot' | 'spotV3'; //| 'v3';
// Same as inverse futures
export type WsPublicInverseTopic =

View File

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

View File

@@ -136,6 +136,17 @@ export class WebsocketClient extends EventEmitter {
this.connectPublic();
break;
}
case 'spotV3': {
this.restClient = new SpotClientV3(
undefined,
undefined,
!this.isTestnet(),
this.options.restOptions,
this.options.requestOptions
);
this.connectPublic();
break;
}
// if (this.isV3()) {
// this.restClient = new SpotClientV3(
// undefined,
@@ -175,59 +186,6 @@ export class WebsocketClient extends EventEmitter {
// 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) {
this.logger.info('Closing connection', { ...loggerCategory, wsKey });
this.setWsState(wsKey, WsConnectionStateEnum.CLOSING);
@@ -263,6 +221,12 @@ export class WebsocketClient extends EventEmitter {
this.connect(WS_KEY_MAP.spotPrivate),
];
}
case 'spotV3': {
return [
this.connect(WS_KEY_MAP.spotV3Public),
this.connect(WS_KEY_MAP.spotV3Private),
];
}
default: {
throw neverGuard(this.options.market, `connectAll(): Unhandled market`);
}
@@ -280,6 +244,9 @@ export class WebsocketClient extends EventEmitter {
case 'spot': {
return this.connect(WS_KEY_MAP.spotPublic);
}
case 'spotV3': {
return this.connect(WS_KEY_MAP.spotV3Public);
}
default: {
throw neverGuard(
this.options.market,
@@ -300,6 +267,9 @@ export class WebsocketClient extends EventEmitter {
case 'spot': {
return this.connect(WS_KEY_MAP.spotPrivate);
}
case 'spotV3': {
return this.connect(WS_KEY_MAP.spotV3Private);
}
default: {
throw neverGuard(
this.options.market,
@@ -503,7 +473,7 @@ export class WebsocketClient extends EventEmitter {
this.tryWsSend(wsKey, wsMessage);
}
private tryWsSend(wsKey: WsKey, wsMessage: string) {
public tryWsSend(wsKey: WsKey, wsMessage: string) {
try {
this.logger.silly(`Sending upstream ws message: `, {
...loggerCategory,
@@ -666,7 +636,13 @@ export class WebsocketClient extends EventEmitter {
return WS_BASE_URL_MAP.spot.public[networkKey];
}
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: {
// private and public are on the same WS connection
@@ -691,7 +667,10 @@ export class WebsocketClient extends EventEmitter {
return getLinearWsKeyForTopic(topic);
}
case 'spot': {
return getSpotWsKeyForTopic(topic);
return getSpotWsKeyForTopic(topic, 'v1');
}
case 'spotV3': {
return getSpotWsKeyForTopic(topic, 'v3');
}
default: {
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.
public subscribePublicSpotTrades(symbol: string, binary?: boolean) {
if (!this.isSpot()) {

View File

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

View File

@@ -9,7 +9,7 @@ import {
WS_OPEN_EVENT_PARTIAL,
} from '../ws.util';
describe('Private Linear Websocket Client', () => {
describe('Private Linear Perps Websocket Client', () => {
const API_KEY = process.env.API_KEY_COM;
const API_SECRET = process.env.API_SECRET_COM;

View File

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