SPB Git forge

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)

127commits 1branches 0releases
24.7 MBsize
maindefault branch
11 days agolast push
JavaScript 53.7% Python 38.3% CSS 4.6% TypeScript 3.1%
4.1 KB · 75 lines typescript
Raw Blame History
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