import { describe, expect, test } from "bun:test"; import { createTestDataProvider } from "../../../test-support/data-provider"; import { createDefaultConfig } from "../../../types/config"; import type { HeadlessPaneContext, HeadlessPaneLoadArgs } from "../../../types/plugin"; import { loadParsedInsiderFilings } from "./client"; import { createInsiderHeadless } from "./headless"; import type { ParsedInsiderFiling } from "./model"; const parsed: ParsedInsiderFiling[] = [ { filing: { accessionNumber: "one", form: "4", filingDate: new Date("2026-08-26T00:00:00.000Z"), cik: "0000002488", filingUrl: "https://www.sec.gov/one", }, transaction: { filingDate: new Date("2026-08-25T00:00:00.000Z"), reportedName: "SU LISA T", title: "Chief Executive Officer", transactionType: "P", shares: 25_000, pricePerShare: 150, totalValue: 3_750_000, sharesOwned: 4_000_000, form: "4", }, isLoading: false, }, { filing: { accessionNumber: "two", form: "4", filingDate: new Date("2026-08-24T00:00:00.000Z"), cik: "0000002488", filingUrl: "https://www.sec.gov/two", }, transaction: { filingDate: new Date("2026-08-23T00:00:00.000Z"), reportedName: "OTHER OFFICER", title: "Officer", transactionType: "S", shares: 1_000, pricePerShare: 155, totalValue: 155_000, sharesOwned: 20_000, form: "4", }, isLoading: false, }, ]; function context(): HeadlessPaneContext { return { marketData: createTestDataProvider(), apiClient: {} as HeadlessPaneContext["apiClient"], config: createDefaultConfig("/tmp/gloomberb-headless-insider"), signal: new AbortController().signal, }; } function args(name = "", limit = 20): HeadlessPaneLoadArgs { return { rawArgument: "AMD", argument: "AMD", symbols: ["AMD"], options: { name, limit }, }; } describe("insider client", () => { test("scans Form 4 filings and loads their content sequentially", async () => { let activeContentLoads = 0; let maxActiveContentLoads = 0; const filingRequests: Array<{ symbol: string; count: number; exchange: string | undefined }> = []; const provider = createTestDataProvider({ getSecFilings: async (symbol, count, exchange) => { filingRequests.push({ symbol, count, exchange }); return parsed.map(({ filing }) => filing); }, getSecFilingContent: async () => { activeContentLoads += 1; maxActiveContentLoads = Math.max(maxActiveContentLoads, activeContentLoads); await Bun.sleep(1); activeContentLoads -= 1; return null; }, }); const result = await loadParsedInsiderFilings(provider, "NVDA", { limit: 2 }); expect(filingRequests).toEqual([{ symbol: "NVDA", count: 20_000, exchange: "" }]); expect(result).toHaveLength(2); expect(maxActiveContentLoads).toBe(1); }); test("filing limit expands every transaction and reports failed content even with an owner filter", async () => { const tx = (shares: number) => `Class A2026-08-20G${shares}`; const provider = createTestDataProvider({ getSecFilings: async () => parsed.map(({ filing }) => filing), getSecFilingContent: async (filing) => filing.accessionNumber === "one" ? `SU LISA T${tx(55)}${tx(294)}` : null, }); const result = await createInsiderHeadless().load(args("su lisa t", 2), { ...context(), marketData: provider }); expect(result.rows.map((row) => [row.id, row.shares, row.side])).toEqual([["one:0", 55, "GIFT"], ["one:1", 294, "GIFT"]]); expect(result.metadata).toMatchObject({ parsed: 1, transactions: 2, requested: 2 }); expect(result.errors).toEqual(["two: Form 4 transactions are unavailable or incomplete."]); }); }); describe("insider headless model", () => { test("projects parsed Form 4 values and applies the owner filter", async () => { const requestedLimits: number[] = []; const headless = createInsiderHeadless({ loadParsed: async (_symbol, limit) => { requestedLimits.push(limit); return parsed.slice(0, limit); }, }); const all = await headless.load(args("", 2), context()); expect(all.rows[0]).toMatchObject({ insider: "SU LISA T", side: "BUY", shares: 25_000, totalValue: 3_750_000, }); const filtered = await headless.load(args("other officer", 2), context()); expect(filtered.rows).toHaveLength(1); expect(filtered.rows[0]).toMatchObject({ insider: "OTHER OFFICER", side: "SELL" }); expect(requestedLimits).toEqual([2, 2]); }); });