Skip to content
markpaper

src/markets/parse.test.ts

v0.2.1 · 4.8 KB

Download file
import { describe, expect, it } from 'vitest';
import { orderBookDetailsPayload, orderBooksPayload } from '../testing/fixtures.js';
import { markPrices, parseOrderBookDetails, parseOrderBooks, symbolByMarketId } from './parse.js';
import { LighterMetaError } from './types.js';

describe('parseOrderBookDetails', () => {
  it('decodes perps, skips other market types, keeps raw strings', () => {
    const m = parseOrderBookDetails(orderBookDetailsPayload());
    expect([...m.keys()]).toEqual(['ETH', 'ETH/USDG', 'LIT', 'CRWV']);
    const eth = m.get('ETH');
    expect(eth).toMatchObject({
      marketId: 32,
      sizeDecimals: 4,
      priceDecimals: 2,
      minBaseAmount: 0.01,
      minBaseAmountRaw: '0.0100',
      minQuoteUsd: 10,
      minQuoteUsdRaw: '10.000000',
      markPrice: 1605.34,
      markPriceRaw: '1605.34',
      lastTradePrice: 1606.1,
      minInitialMarginFraction: 1000,
      defaultInitialMarginFraction: 5000,
      maxLeverage: 10,
      status: 'active',
      isUsdgDuplicate: false,
    });
    expect(m.get('ETH/USDG')?.isUsdgDuplicate).toBe(true);
    expect(m.get('LIT')).toMatchObject({ sizeDecimals: 0, minBaseAmount: 5, maxLeverage: 5 });
  });

  it('can include spot markets on request', () => {
    const m = parseOrderBookDetails(orderBookDetailsPayload(), { marketTypes: ['perp', 'spot'] });
    expect(m.get('PURR')?.marketType).toBe('spot');
  });

  it('fails closed on a missing array', () => {
    expect(() => parseOrderBookDetails({})).toThrow(LighterMetaError);
    expect(() => parseOrderBookDetails(null)).toThrow(LighterMetaError);
    expect(() => parseOrderBookDetails({ order_book_details: 'nope' })).toThrow(LighterMetaError);
  });

  it('fails closed on a duplicate symbol or market_id', () => {
    const dupSymbol = orderBookDetailsPayload();
    const rows = dupSymbol.order_book_details as Array<Record<string, unknown>>;
    rows.push({ ...(rows[0] as Record<string, unknown>), market_id: 777 });
    expect(() => parseOrderBookDetails(dupSymbol)).toThrow(/duplicate symbol/);

    const dupId = orderBookDetailsPayload();
    const rows2 = dupId.order_book_details as Array<Record<string, unknown>>;
    rows2.push({ ...(rows2[0] as Record<string, unknown>), symbol: 'OTHER' });
    expect(() => parseOrderBookDetails(dupId)).toThrow(/duplicate market_id/);
  });

  it('fails closed on a record without decimals or with a broken id', () => {
    const noDecimals = orderBookDetailsPayload();
    const rows = noDecimals.order_book_details as Array<Record<string, unknown>>;
    delete (rows[0] as Record<string, unknown>).supported_size_decimals;
    expect(() => parseOrderBookDetails(noDecimals)).toThrow(/decimals/);

    const badId = orderBookDetailsPayload();
    (badId.order_book_details as Array<Record<string, unknown>>)[0] = {
      symbol: 'X',
      market_id: 'abc',
      market_type: 'perp',
    };
    expect(() => parseOrderBookDetails(badId)).toThrow(/broken record/);
  });

  it('fails closed when no market of the requested type exists', () => {
    expect(() => parseOrderBookDetails({ order_book_details: [] })).toThrow(LighterMetaError);
    expect(() => parseOrderBookDetails(orderBookDetailsPayload(), { marketTypes: ['option'] })).toThrow(
      LighterMetaError,
    );
  });

  it('treats missing minimums / mark as zero, not as NaN', () => {
    const m = parseOrderBookDetails({
      order_book_details: [
        { symbol: 'X', market_id: 1, market_type: 'perp', supported_size_decimals: 1, supported_price_decimals: 1 },
      ],
    });
    expect(m.get('X')).toMatchObject({ minBaseAmount: 0, minQuoteUsd: 0, markPrice: 0, maxLeverage: 1, status: '' });
  });

  it('symbolByMarketId and markPrices derive lookup maps', () => {
    const m = parseOrderBookDetails(orderBookDetailsPayload());
    expect(symbolByMarketId(m.values()).get(41)).toBe('CRWV');
    expect(markPrices(m.values()).get('LIT')).toBe(0.912);
  });
});

describe('parseOrderBooks', () => {
  it('decodes the flat list and flags duplicates of the quote currency', () => {
    const rows = parseOrderBooks(orderBooksPayload());
    expect(rows.map((r) => r.symbol)).toEqual(['ETH', 'ETH/USDG', 'LIT', 'CRWV']);
    expect(rows[1]?.isUsdgDuplicate).toBe(true);
    expect(rows[0]?.raw.taker_fee).toBe('0.0000');
  });

  it('fails closed on a missing array, a broken row or duplicates', () => {
    expect(() => parseOrderBooks({})).toThrow(LighterMetaError);
    expect(() => parseOrderBooks({ order_books: [{ market_id: 1 }] })).toThrow(/broken row/);
    expect(() =>
      parseOrderBooks({
        order_books: [
          { symbol: 'A', market_id: 1 },
          { symbol: 'A', market_id: 2 },
        ],
      }),
    ).toThrow(/duplicate symbol/);
    expect(() =>
      parseOrderBooks({
        order_books: [
          { symbol: 'A', market_id: 1 },
          { symbol: 'B', market_id: 1 },
        ],
      }),
    ).toThrow(/duplicate market_id/);
  });
});
All files