spb/hfmarketdata
Public
Open high-frequency market data platform — FirstRate full-history downloader, DuckDB/Parquet lake, open REST API and React docs platform (www.hfmarketdata.io)
JavaScript 53.7%
Python 38.3%
CSS 4.6%
TypeScript 3.1%
1import { describe, expect, it } from 'vitest';2import { HfmdClient, HfmdError, errorFromResponse, rateFromHeaders } from '../src/client.js';3import { RATE_HEADERS, mockFetch } from './helpers.js';45describe('HfmdClient', () => {6 it('builds URLs with format=json and joins array params', () => {7 const c = new HfmdClient({ baseUrl: 'https://api.test/', apiKey: '', fetchImpl: fetch });8 const u = c.buildUrl('/v1/bars/stock', { tickers: ['AAPL', 'MSFT'], limit: 10, start: undefined, end: '' });9 expect(u).toBe('https://api.test/v1/bars/stock?tickers=AAPL%2CMSFT&limit=10&format=json');10 expect(c.wsUrl).toBe('wss://api.test/v1/stream');11 });1213 it('sends the bearer header only when a key is set', async () => {14 let seen: Headers | undefined;15 const f = (async (_u: any, init?: RequestInit) => {16 seen = new Headers(init?.headers as any);17 return new Response('{"ok":true}', { headers: { 'content-type': 'application/json' } });18 }) as typeof fetch;19 await new HfmdClient({ baseUrl: 'https://api.test', apiKey: 'hfmd_live_x', fetchImpl: f }).get('/v1/status');20 expect(seen?.get('authorization')).toBe('Bearer hfmd_live_x');21 await new HfmdClient({ baseUrl: 'https://api.test', apiKey: '', fetchImpl: f }).get('/v1/status');22 expect(seen?.get('authorization')).toBeNull();23 expect(seen?.get('user-agent')).toContain('hfmarketdata-mcp');24 });2526 it('parses rate-limit headers', async () => {27 const m = mockFetch([{ match: '/v1/status', body: { ok: 1 }, headers: RATE_HEADERS }]);28 const r = await new HfmdClient({ baseUrl: 'https://api.test', apiKey: '', fetchImpl: m.fetch }).get('/v1/status');29 expect(r.rate.remainingRequests).toBe(29);30 expect(r.rate.limitRows).toBe(100000);31 expect(r.rate.rowCount).toBe(5);32 expect(rateFromHeaders(new Headers()).remainingRequests).toBeNull();33 });3435 it('maps v2 error envelopes', async () => {36 const m = mockFetch([{ match: '/v1/futures/XX/chain', status: 404, body: { error: { code: 'ROOT_NOT_FOUND', message: 'Unknown futures root XX', docs: 'https://d/errors#root_not_found' }, detail: 'Unknown futures root XX' } }]);37 const c = new HfmdClient({ baseUrl: 'https://api.test', apiKey: '', fetchImpl: m.fetch });38 const err = await c.get('/v1/futures/XX/chain').catch((e) => e);39 expect(err).toBeInstanceOf(HfmdError);40 expect(err.status).toBe(404);41 expect(err.code).toBe('ROOT_NOT_FOUND');42 expect(err.describe()).toContain('ROOT_NOT_FOUND');43 expect(err.describe()).toContain('https://d/errors#root_not_found');44 });4546 it('maps legacy v1 {detail} errors and validation arrays', () => {47 const e1 = errorFromResponse(404, { detail: 'Unknown ticker' }, rateFromHeaders(new Headers()), 'u');48 expect(e1.code).toBe('NOT_FOUND');49 expect(e1.message).toBe('Unknown ticker');50 const e2 = errorFromResponse(422, { detail: [{ loc: ['query', 'limit'], msg: 'must be > 0' }] }, rateFromHeaders(new Headers()), 'u');51 expect(e2.code).toBe('INVALID_PARAMETER');52 expect(e2.message).toContain('query.limit: must be > 0');53 const e3 = errorFromResponse(503, 'Service Unavailable', rateFromHeaders(new Headers()), 'u');54 expect(e3.code).toBe('INTERNAL_ERROR');55 });5657 it('explains 429 with Retry-After and the keyless hint', async () => {58 const m = mockFetch([{ match: '/v1/bars', status: 429, body: { error: { code: 'RATE_LIMIT_EXCEEDED', message: 'quota exhausted' } }, headers: { 'retry-after': '41', 'x-ratelimit-remaining-requests': '0' } }]);59 const c = new HfmdClient({ baseUrl: 'https://api.test', apiKey: '', fetchImpl: m.fetch });60 const err: HfmdError = await c.get('/v1/bars/stock/AAPL').catch((e) => e);61 expect(err.status).toBe(429);62 const text = err.describe();63 expect(text).toContain('Retry after 41s');64 expect(text).toContain('HFMD_API_KEY');65 });6667 it('wraps network failures', async () => {68 const f = (async () => { throw new TypeError('fetch failed'); }) as unknown as typeof fetch;69 const c = new HfmdClient({ baseUrl: 'https://api.test', apiKey: '', fetchImpl: f });70 const err: HfmdError = await c.get('/v1/status').catch((e) => e);71 expect(err.code).toBe('NETWORK_ERROR');72 expect(err.status).toBe(0);73 });74});75