SPB Git forge

spb/market-atlas

Public
12commits 1branches 0releases
1.1 MBsize
maindefault branch
10 days agolast push
TypeScript 96.7% SQL 1.6% CSS 0.8% JavaScript 0.5%
3.7 KB · 92 lines typescript
Raw Blame History
1import { readFileSync } from "node:fs";2import { join } from "node:path";3import type { RawObservation } from "@market-atlas/market-model";4import { NormalizedObservationSchema } from "@market-atlas/market-model";5import { HttpClient } from "./http.js";6import { RateLimiter } from "./ratelimit.js";7import type { ConnectorContext, ConnectorDefinition, ConnectorLogger, NormalizedBatch } from "./types.js";8import { ManagedWebSocket, type ManagedWebSocketOptions } from "./ws.js";910/** Load a fixture file (JSON parsed when the extension is .json, raw text otherwise). */11export function loadFixture(dir: string, name: string): unknown {12  const text = readFileSync(join(dir, name), "utf8");13  return name.endsWith(".json") ? JSON.parse(text) : text;14}1516export function rawFromFixture(def: ConnectorDefinition, kind: string, payload: unknown, receivedAt = 1_789_200_000_000): RawObservation {17  return { connectorId: def.metadata.id, sourceId: def.metadata.sourceId, kind, payload, receivedAt };18}1920/** Runs normalize() on a payload and validates every observation against the canonical schema. */21export function normalizeFixture(def: ConnectorDefinition, kind: string, payload: unknown): NormalizedBatch {22  const batch = def.normalize(rawFromFixture(def, kind, payload));23  for (const o of batch.observations) {24    const res = NormalizedObservationSchema.safeParse(o);25    if (!res.success) throw new Error(`invalid observation from ${def.metadata.id}: ${res.error.message}`);26  }27  return batch;28}2930const silentLogger: ConnectorLogger = { debug() {}, info() {}, warn() {}, error() {} };3132/** Offline connector context for tests: no network unless a fetchImpl is provided. */33export function makeTestContext(34  def: ConnectorDefinition,35  opts: { fetchImpl?: typeof fetch; marketOpen?: boolean; secrets?: Record<string, string>; symbols?: string[] } = {},36): ConnectorContext & { emitted: RawObservation[]; errors: unknown[]; sockets: ManagedWebSocket[] } {37  const emitted: RawObservation[] = [];38  const errors: unknown[] = [];39  const sockets: ManagedWebSocket[] = [];40  const store = new Map<string, unknown>();41  const http = new HttpClient({42    userAgent: "MarketAtlas-test/0.1",43    limiter: new RateLimiter({ ratePerSec: 1000 }),44    fetchImpl:45      opts.fetchImpl ??46      (async () => {47        throw new Error("network disabled in tests");48      }),49  });50  return {51    connectorId: def.metadata.id,52    logger: silentLogger,53    http,54    state: {55      async get(k) {56        return store.get(k) as never;57      },58      async set(k, v) {59        store.set(k, v);60      },61    },62    emit(r) {63      emitted.push(...(Array.isArray(r) ? r : [r]));64    },65    openWebSocket(url: string, o: ManagedWebSocketOptions) {66      const s = new ManagedWebSocket(url, o);67      sockets.push(s);68      return s;69    },70    isMarketOpen: () => opts.marketOpen ?? true,71    watchedSymbols: () => opts.symbols ?? def.defaultSymbols ?? [],72    reportError: (e) => errors.push(e),73    secret: (n) => opts.secrets?.[n],74    now: () => Date.now(),75    emitted,76    errors,77    sockets,78  };79}8081/** Build a fake fetch that serves canned responses by URL substring. */82export function fakeFetch(routes: Record<string, string | { status?: number; body: string; headers?: Record<string, string> }>): typeof fetch {83  return (async (input: string | URL | Request) => {84    const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;85    const key = Object.keys(routes).find((k) => url.includes(k));86    if (!key) return new Response("not found", { status: 404 });87    const r = routes[key]!;88    const spec = typeof r === "string" ? { body: r } : r;89    return new Response(spec.body, { status: spec.status ?? 200, headers: spec.headers ?? {} });90  }) as typeof fetch;91}92