src/markets/markets.test.ts
v0.1.0 · 3 KB
import { describe, expect, it } from 'vitest';
import { fixtureRefdata } from '../testing/fixtures.js';
import {
createMarketCache,
createQuant,
decodeRefdata,
listingOf,
mergeRefdata,
parseSymbol,
selectBand,
} from './index.js';
describe('reference data and market grids', () => {
it('preserves symbol case and rejects mismatched quote semantics', () => {
expect(parseSymbol('kTEST-USD')).toEqual({ base: 'kTEST', quote: 'USD' });
const market = decodeRefdata([fixtureRefdata()]).markets.get('BTC-USD')!;
expect(listingOf({ ...market, quote: 'EUR' }).status).toBe('unlisted');
expect(listingOf({ ...market, status: 'FUTURE_STATUS' }).status).toBe('unknown');
});
it('quantizes nondecimal lot and tick sizes without float drift', () => {
const quant = createQuant(decodeRefdata([fixtureRefdata()]).markets.get('BTC-USD')!);
expect(quant.szToStr(0.37999999999999995)).toBe('0.38');
expect(quant.pxToStrDir(120.24999999999999, 'down')).toBe('120.25');
expect(quant.minSz).toBe(0.06);
});
it('rejects metadata that cannot produce finite numeric quantities', () => {
const market = decodeRefdata([fixtureRefdata()]).markets.get('BTC-USD')!;
expect(() => createQuant({ ...market, minQty: '1e400' })).toThrow();
});
it('rejects duplicate symbols rather than selecting one', () => {
const rows = [
fixtureRefdata('BTC-USD'),
fixtureRefdata('BTC-USD'),
fixtureRefdata('ETH-USD'),
fixtureRefdata('TESTA-USD'),
fixtureRefdata('TESTB-USD'),
];
const decoded = decodeRefdata(rows);
expect(decoded.bad.has('BTC-USD')).toBe(true);
expect(decoded.markets.has('BTC-USD')).toBe(false);
});
it('freezes only a watched changed grid, preserving other updates', () => {
const before = decodeRefdata([fixtureRefdata(), fixtureRefdata('ETH-USD')]);
const after = decodeRefdata([{ ...fixtureRefdata(), lot_size: '0.04' }, fixtureRefdata('ETH-USD')]);
const merged = mergeRefdata(before.markets, after, ['BTC-USD']);
expect(merged.frozen.get('BTC-USD')?.reason).toBe('grid_changed');
expect(merged.markets.get('BTC-USD')?.lot).toBe('0.02');
expect(merged.markets.has('ETH-USD')).toBe(true);
});
it('returns stale refdata bands for display only', () => {
expect(selectBand({ refBand: { min: '40', max: '240' }, freshMs: 30, now: 100 })).toMatchObject({
fresh: false,
source: 'refdata',
});
});
it('deduplicates refreshes and keeps stale cache failure visible', async () => {
let calls = 0,
now = 0;
const cache = createMarketCache({
ttlMs: 10,
retryBackoffMs: 20,
watched: () => [],
now: () => now,
fetchRefdata: async () => {
calls++;
if (calls > 1) throw new Error('Unavailable');
return { payload: [fixtureRefdata()] };
},
});
await Promise.all([cache.get(), cache.get()]);
expect(calls).toBe(1);
now = 20;
await cache.get();
expect(cache.info().fresh).toBe(false);
expect(cache.info().lastError).toBe('Unavailable');
});
});