pacificaWsClient.test.ts 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. import { beforeEach, afterEach, describe, expect, it, vi } from 'vitest';
  2. import { WebSocketServer } from 'ws';
  3. import { PacificaWebSocket } from '../packages/connectors/pacifica/src/wsClient';
  4. import nacl from 'tweetnacl';
  5. import bs58 from 'bs58';
  6. import { signRequest } from '../packages/connectors/pacifica/src/signing';
  7. describe('PacificaWebSocket', () => {
  8. let server: WebSocketServer;
  9. let port: number;
  10. beforeEach(() => {
  11. server = new WebSocketServer({ port: 0 });
  12. const address = server.address();
  13. if (typeof address === 'string' || !address) {
  14. throw new Error('Failed to obtain server address');
  15. }
  16. port = address.port;
  17. });
  18. afterEach(async () => {
  19. for (const client of server.clients) {
  20. client.close();
  21. }
  22. await new Promise(resolve => server.close(resolve));
  23. vi.restoreAllMocks();
  24. });
  25. it('connects, subscribes, and receives heartbeat responses', async () => {
  26. const messages: string[] = [];
  27. const client = new PacificaWebSocket({
  28. url: `ws://127.0.0.1:${port}`,
  29. heartbeatIntervalMs: 10
  30. });
  31. server.on('connection', socket => {
  32. socket.on('message', data => {
  33. messages.push(data.toString());
  34. socket.send(JSON.stringify({ method: 'pong' }));
  35. });
  36. });
  37. const messagePromise = new Promise<string>(resolve => {
  38. client.once('message', data => resolve(data.toString()));
  39. });
  40. client.subscribe('book.BTC', { depth: 1 });
  41. client.connect();
  42. // wait for server to receive login & subscribe
  43. await new Promise(resolve => setTimeout(resolve, 50));
  44. expect(messages.length).toBeGreaterThanOrEqual(1);
  45. const subscribe = JSON.parse(messages[0]!);
  46. expect(subscribe.method).toBe('subscribe');
  47. expect(subscribe.params.channel).toBe('book.BTC');
  48. const serverMessage = await messagePromise;
  49. expect(serverMessage).toContain('pong');
  50. client.disconnect();
  51. });
  52. it('throws when subscribing to private channel without credentials', () => {
  53. const client = new PacificaWebSocket({
  54. url: `ws://127.0.0.1:${port}`
  55. });
  56. expect(() => client.subscribe('orders.sub-1')).toThrow(
  57. 'Missing credentials for private Pacifica channel orders.sub-1'
  58. );
  59. });
  60. it('injects auth params for private channel subscriptions', async () => {
  61. const seed = Uint8Array.from({ length: 32 }, (_, i) => i + 1);
  62. const { secretKey, publicKey } = nacl.sign.keyPair.fromSeed(seed);
  63. const secret = Buffer.from(secretKey).toString('base64');
  64. const apiKey = bs58.encode(publicKey);
  65. vi.spyOn(Date, 'now').mockReturnValue(1_700_000_000_000);
  66. const messages: string[] = [];
  67. const client = new PacificaWebSocket({
  68. url: `ws://127.0.0.1:${port}`,
  69. apiKey,
  70. secret,
  71. subaccount: 'sub-1',
  72. heartbeatIntervalMs: 10
  73. });
  74. server.on('connection', socket => {
  75. socket.on('message', data => {
  76. messages.push(data.toString());
  77. });
  78. });
  79. const basePayload = {
  80. method: 'subscribe' as const,
  81. params: { channel: 'orders.sub-1' }
  82. };
  83. const { headers, timestamp } = signRequest(
  84. { apiKey, secret, subaccount: 'sub-1' },
  85. 'SUBSCRIBE',
  86. '/ws',
  87. basePayload
  88. );
  89. const expectedSignature = headers['X-Pacific-Signature'];
  90. client.subscribe('orders.sub-1');
  91. client.connect();
  92. await new Promise(resolve => setTimeout(resolve, 50));
  93. expect(messages.length).toBeGreaterThanOrEqual(1);
  94. const subscribe = JSON.parse(messages[0]!);
  95. expect(subscribe.method).toBe('subscribe');
  96. expect(subscribe.params.channel).toBe('orders.sub-1');
  97. expect(subscribe.params.auth).toEqual({
  98. key: apiKey,
  99. timestamp,
  100. signature: expectedSignature,
  101. subaccount: 'sub-1'
  102. });
  103. client.disconnect();
  104. });
  105. });