import { beforeEach, describe, expect, it, mock } from "bun:test"; import type { TaxonomyHydrationInput, TaxonomyHydrationResult, } from "@/lib/server/taxonomy/types"; import { __parserClientInternals } from "@/lib/server/taxonomy/parser-client"; function streamFromText(text: string) { const encoded = new TextEncoder().encode(text); return new ReadableStream({ start(controller) { controller.enqueue(encoded); controller.close(); }, }); } function sampleHydrationResult(): TaxonomyHydrationResult { return { filing_id: 1, ticker: "AAPL", filing_date: "2026-01-30", filing_type: "10-Q", parse_status: "ready", parse_error: null, source: "xbrl_instance", parser_engine: "fiscal-xbrl", parser_version: "0.1.0", taxonomy_regime: "us-gaap", fiscal_pack: "core", periods: [], faithful_rows: { income: [], balance: [], cash_flow: [], disclosure: [], equity: [], comprehensive_income: [], }, statement_rows: { income: [], balance: [], cash_flow: [], disclosure: [], equity: [], comprehensive_income: [], }, surface_rows: { income: [], balance: [], cash_flow: [], disclosure: [], equity: [], comprehensive_income: [], }, detail_rows: { income: {}, balance: {}, cash_flow: {}, disclosure: {}, equity: {}, comprehensive_income: {}, }, kpi_rows: [], computed_definitions: [], contexts: [], derived_metrics: null, validation_result: null, facts_count: 0, concepts_count: 0, dimensions_count: 0, assets: [], concepts: [], facts: [], metric_validations: [], normalization_summary: { surface_row_count: 0, detail_row_count: 0, kpi_row_count: 0, unmapped_row_count: 0, material_unmapped_row_count: 0, warnings: [], }, xbrl_validation: { status: "passed", }, }; } function sampleInput(): TaxonomyHydrationInput { return { filingId: 1, ticker: "AAPL", cik: "0000320193", accessionNumber: "0000320193-26-000001", filingDate: "2026-01-30", filingType: "10-Q", filingUrl: "https://www.sec.gov/Archives/edgar/data/320193/000032019326000001/", primaryDocument: "a10q.htm", }; } const passThroughTimeout = ((handler: TimerHandler, timeout?: number) => globalThis.setTimeout( handler, timeout, )) as unknown as typeof globalThis.setTimeout; const immediateTimeout = ((handler: TimerHandler) => { if (typeof handler === "function") { handler(); } return 1 as unknown as ReturnType; }) as unknown as typeof globalThis.setTimeout; describe("parser client", () => { beforeEach(() => { delete process.env.FISCAL_XBRL_BIN; delete process.env.XBRL_ENGINE_TIMEOUT_MS; }); it("throws when the sidecar binary cannot be resolved", () => { expect(() => __parserClientInternals.resolveFiscalXbrlBinary({ existsSync: () => false, }), ).toThrow(/Rust XBRL sidecar binary is required/); }); it("returns parsed sidecar JSON on success", async () => { const stdinWrite = mock(() => {}); const stdinEnd = mock(() => {}); const result = await __parserClientInternals.hydrateFromSidecarImpl( sampleInput(), { existsSync: () => true, spawn: mock(() => ({ stdin: { write: stdinWrite, end: stdinEnd, }, stdout: streamFromText(JSON.stringify(sampleHydrationResult())), stderr: streamFromText(""), exited: Promise.resolve(0), kill: mock(() => {}), })) as never, setTimeout: passThroughTimeout, clearTimeout, }, ); expect(result.parser_engine).toBe("fiscal-xbrl"); expect(stdinWrite).toHaveBeenCalledTimes(1); expect(stdinEnd).toHaveBeenCalledTimes(1); }); it("throws when the sidecar exits non-zero", async () => { await expect( __parserClientInternals.hydrateFromSidecarImpl(sampleInput(), { existsSync: () => true, spawn: mock(() => ({ stdin: { write: () => {}, end: () => {}, }, stdout: streamFromText(""), stderr: streamFromText("fatal parse error"), exited: Promise.resolve(3), kill: mock(() => {}), })) as never, setTimeout: passThroughTimeout, clearTimeout, }), ).rejects.toThrow(/exit code 3/); }); it("throws on invalid JSON stdout", async () => { await expect( __parserClientInternals.hydrateFromSidecarImpl(sampleInput(), { existsSync: () => true, spawn: mock(() => ({ stdin: { write: () => {}, end: () => {}, }, stdout: streamFromText("{not json"), stderr: streamFromText(""), exited: Promise.resolve(0), kill: mock(() => {}), })) as never, setTimeout: passThroughTimeout, clearTimeout, }), ).rejects.toThrow(); }); it("kills the sidecar when the timeout fires", async () => { const kill = mock(() => {}); await expect( __parserClientInternals.hydrateFromSidecarImpl(sampleInput(), { existsSync: () => true, spawn: mock(() => ({ stdin: { write: () => {}, end: () => {}, }, stdout: streamFromText(""), stderr: streamFromText("killed"), exited: Promise.resolve(137), kill, })) as never, setTimeout: immediateTimeout, clearTimeout: () => {}, }), ).rejects.toThrow(/exit code 137/); expect(kill).toHaveBeenCalledTimes(1); }); it("retries retryable sidecar failures but not invalid requests", async () => { let attempts = 0; const spawn = mock(() => { attempts += 1; const exitCode = attempts < 3 ? 1 : 0; const stdout = exitCode === 0 ? JSON.stringify(sampleHydrationResult()) : ""; const stderr = exitCode === 0 ? "" : "process killed"; return { stdin: { write: () => {}, end: () => {}, }, stdout: streamFromText(stdout), stderr: streamFromText(stderr), exited: Promise.resolve(exitCode), kill: mock(() => {}), }; }); const result = await __parserClientInternals.hydrateFilingTaxonomySnapshotFromSidecarWithDeps( sampleInput(), { existsSync: () => true, spawn: spawn as never, setTimeout: passThroughTimeout, clearTimeout, }, ); expect(result.parser_version).toBe("0.1.0"); expect(attempts).toBe(3); attempts = 0; const invalidRequestSpawn = mock(() => { attempts += 1; return { stdin: { write: () => {}, end: () => {}, }, stdout: streamFromText(""), stderr: streamFromText("invalid request: bad command"), exited: Promise.resolve(6), kill: mock(() => {}), }; }); await expect( __parserClientInternals.hydrateFilingTaxonomySnapshotFromSidecarWithDeps( sampleInput(), { existsSync: () => true, spawn: invalidRequestSpawn as never, setTimeout: passThroughTimeout, clearTimeout, }, ), ).rejects.toThrow(/invalid request/); expect(attempts).toBe(1); }); });