Skip to content
markpaper

src/ws/errors.test.ts

v0.3.0 · 4.4 KB

Download file
import { describe, expect, it } from 'vitest';
import { classifyWsServerError, WsLimitError } from './errors.js';

describe('classifyWsServerError', () => {
  it('recognizes the user-tracking capacity refusal and keeps the socket', () => {
    expect(classifyWsServerError('Cannot track more than 15 total users')).toEqual({ kind: 'userLimit', limit: 15, terminate: false });
  });

  it('matches case-insensitively and without a number', () => {
    expect(classifyWsServerError('CANNOT TRACK MORE THAN many users').kind).toBe('userLimit');
    expect(classifyWsServerError('cannot track more than 10 total users').limit).toBe(10);
  });

  it('recognizes the subscription cap text', () => {
    expect(classifyWsServerError('Cannot subscribe to more than 1000 channels.')).toMatchObject({
      kind: 'subscriptionLimit',
      limit: 1000,
      terminate: false,
    });
  });

  it('already subscribed / unsubscribed are not fatal', () => {
    expect(classifyWsServerError('Already subscribed: {"type":"l2Book","coin":"BTC"}')).toEqual({
      kind: 'alreadySubscribed',
      terminate: false,
      echo: { type: 'l2Book', coin: 'BTC' },
    });
    expect(classifyWsServerError('Already unsubscribed: {"type":"trades","coin":"ETH"}').kind).toBe('alreadyUnsubscribed');
  });

  it('invalid subscription with an echo is attributable and does not terminate', () => {
    const info = classifyWsServerError('Invalid subscription {"type":"userFills","user":"0xBAD"}');
    expect(info.kind).toBe('invalidSubscription');
    expect(info.terminate).toBe(false);
    expect(info.echo).toEqual({ type: 'userFills', user: '0xBAD' });
  });

  it('extracts the subscription from a method envelope echo', () => {
    const info = classifyWsServerError('Invalid subscription {"method":"subscribe","subscription":{"type":"bbo","coin":"X"}}');
    expect(info.echo).toEqual({ type: 'bbo', coin: 'X' });
  });

  it('invalid subscription without echo terminates', () => {
    expect(classifyWsServerError('Invalid subscription').terminate).toBe(true);
  });

  it('unknown errors terminate (HL can leave a failed socket open)', () => {
    expect(classifyWsServerError('Something went wrong')).toEqual({ kind: 'other', terminate: true });
  });

  it('survives non-string payloads and broken JSON echoes', () => {
    expect(classifyWsServerError({ foo: 1 }).kind).toBe('other');
    expect(classifyWsServerError(undefined).kind).toBe('other');
    expect(classifyWsServerError('Invalid subscription {not json}').echo).toBeUndefined();
    expect(classifyWsServerError('Invalid subscription {"a":1}').echo).toBeUndefined();
  });
});

describe('WsLimitError', () => {
  it('carries reason, limit and usage', () => {
    const e = new WsLimitError('maxUniqueUsers', 'msg', 10, 10);
    expect(e).toBeInstanceOf(Error);
    expect(e.name).toBe('WsLimitError');
    expect([e.reason, e.limit, e.used]).toEqual(['maxUniqueUsers', 10, 10]);
  });
});

describe('classifyWsServerError: texts captured on the live socket (2026-09-15)', () => {
  it('request-parse refusal with a subscription echo is an attributable invalid subscription', () => {
    const info = classifyWsServerError(
      'Error parsing JSON into valid websocket request: {"method":"subscribe","subscription":{"type":"candle","coin":"BTC","interval":"7m"}}',
    );
    expect(info).toEqual({ kind: 'invalidSubscription', terminate: false, echo: { type: 'candle', coin: 'BTC', interval: '7m' } });
  });

  it('request-parse refusal without an attributable subscription terminates', () => {
    expect(classifyWsServerError('Error parsing JSON into valid websocket request: not json')).toMatchObject({
      kind: 'invalidSubscription',
      terminate: true,
    });
    expect(classifyWsServerError('Error parsing JSON into valid websocket request: {"method":"subscribe"}').terminate).toBe(true);
  });

  it('"Invalid subscription {json}" exactly as sent by the server', () => {
    expect(classifyWsServerError('Invalid subscription {"type":"activeAssetCtx","coin":"NOPE_COIN"}')).toEqual({
      kind: 'invalidSubscription',
      terminate: false,
      echo: { type: 'activeAssetCtx', coin: 'NOPE_COIN' },
    });
  });

  it('"Already subscribed" for allMids with dex "" echoes the main-dex form', () => {
    expect(classifyWsServerError('Already subscribed: {"type":"allMids"}')).toEqual({
      kind: 'alreadySubscribed',
      terminate: false,
      echo: { type: 'allMids' },
    });
  });
});
All files