import { describe, expect, test } from "bun:test";
import { createTestDataProvider } from "../../../test-support/data-provider";
import { createDefaultConfig } from "../../../types/config";
import { buildHeadlessFunctionReport } from "../../../cli/pane-functions/headless";
import { insiderHeadless } from "./headless";
import { buildInsiderRows, buildInsiderSummary, insiderReportedName, matchesInsiderOwner, parseInsiderFiling } from "./model";
import { buildInsiderAmendmentScopes } from "./amendments";
import type { SecFilingItem } from "../../../types/data-provider";
const now = new Date("2026-09-10T00:00:00Z").getTime();
function filing(id: string, form = "4", date = "2026-08-20"): SecFilingItem {
return { accessionNumber: id, form, filingDate: new Date(`${date}T00:00:00Z`), cik: "999", filingUrl: `https://www.sec.gov/${id}` };
}
function xml({ form = "4", owner = "A. OFFICER", cik = "000111", shares = 100, original = "", security = "Class A", noTransactions = false, note = "Open market purchase." } = {}) {
return `${form}${original}
${owner}${cik}
${noTransactions ? "" : `${security}2026-08-19P${shares}10`}
${note}${note}`;
}
const original = parseInsiderFiling(filing("original"), xml());
const independent = parseInsiderFiling(filing("independent"), xml({ owner: "B. OFFICER", cik: "222", shares: 500, security: "Class B" }));
async function report(entries: Array<{ filing: SecFilingItem; content: string | null }>, name = "") {
const provider = createTestDataProvider({
getSecFilings: async () => entries.map(({ filing }) => filing),
getSecFilingContent: async (item) => entries.find(({ filing }) => filing.accessionNumber === item.accessionNumber)?.content ?? null,
});
return buildHeadlessFunctionReport({ headless: insiderHeadless, token: "INS", label: "Insider", options: { name, limit: 20 }, instance: { settings: {} }, capability: { id: "insider" } } as never,
{ dataProvider: provider, config: createDefaultConfig("/tmp/unused-insider-amendment-test") } as never, "CONTROL");
}
describe("as-filed insider amendments", () => {
test.each([40, 25])("correction or addition of %i shares remains separate and cannot silently enter aggregates", (shares) => {
const amended = parseInsiderFiling(filing("amendment", "4/A", "2026-08-21"), xml({ form: "4/A", cik: "111", shares, original: "2026-08-20" }));
const all = [...amended, ...original, ...independent];
expect(buildInsiderAmendmentScopes(all)).toEqual([{ accessionNumber: "amendment", originalFilingDate: "2026-08-20", originalFilingDateUsable: true, ownerCiks: ["111"], ownerIdentityComplete: true, candidateOriginalAccessions: ["original"] }]);
const rows = buildInsiderRows(all);
expect(rows.map((row) => [row.id, row.shares, row.amendmentStatus])).toEqual([["amendment:0", shares, "unreconciled"], ["original:0", 100, "potentially-amended"], ["independent:0", 500, null]]);
expect(buildInsiderSummary(all, now)).toBe("Loaded filings, last 90 days: Class A: Buy total unavailable (unreconciled amendment) | Class B: Bought 500 shares ($5,000.00)");
expect(buildInsiderSummary(independent, now, all)).toBe("Loaded filings, last 90 days: Class B: Bought 500 shares ($5,000.00)");
});
test("declared original date preserves independent filing dates and unknown date broadens only the possible owner history", () => {
const earlier = parseInsiderFiling(filing("earlier", "4", "2026-08-18"), xml({ shares: 20 }));
const later = parseInsiderFiling(filing("later", "4", "2026-08-22"), xml({ shares: 30 }));
const amended = (date: string, cik = "111") => parseInsiderFiling(filing("amendment", "4/A", "2026-08-21"), xml({ form: "4/A", original: date, cik }));
const context = [...original, ...earlier, ...later, ...independent];
expect(buildInsiderAmendmentScopes([...amended("2026-08-20"), ...context])[0]?.candidateOriginalAccessions).toEqual(["original"]);
expect(buildInsiderAmendmentScopes([...amended("2026-02-31"), ...context])[0]?.candidateOriginalAccessions).toEqual(["original", "earlier"]);
expect(buildInsiderAmendmentScopes([...amended("", ""), ...context])[0]?.candidateOriginalAccessions).toEqual(["original", "earlier", "independent"]);
expect(buildInsiderAmendmentScopes([...amended("2027-08-20"), ...context])[0]).toMatchObject({ originalFilingDate: "2027-08-20", originalFilingDateUsable: false, candidateOriginalAccessions: ["original", "earlier"] });
});
test("partially identified joint owners cannot establish independent amendments or originals", () => {
const joint = (content: string) => content.replace("", "UNKNOWN OWNER");
const amendment = parseInsiderFiling(filing("amendment", "4/A", "2026-08-21"), xml({ form: "4/A", original: "2026-08-20" }));
const partialAmendment = parseInsiderFiling(filing("amendment", "4/A", "2026-08-21"), joint(xml({ form: "4/A", original: "2026-08-20" })));
const partialOriginal = parseInsiderFiling(filing("independent"), joint(xml({ owner: "B. OFFICER", cik: "222", shares: 500, security: "Class B" })));
expect(buildInsiderAmendmentScopes([...partialAmendment, ...independent])[0]).toMatchObject({ ownerIdentityComplete: false, candidateOriginalAccessions: ["independent"] });
expect(buildInsiderAmendmentScopes([...amendment, ...partialOriginal])[0]?.candidateOriginalAccessions).toEqual(["independent"]);
expect(buildInsiderAmendmentScopes([...amendment, ...independent])[0]?.candidateOriginalAccessions).toEqual([]);
});
test("a joint-owner explanation matches both its generated filter name and each individual owner", async () => {
const content = xml({ form: "4/A", original: "2026-08-20", noTransactions: true })
.replace("", "B. OFFICER222");
const entry = parseInsiderFiling(filing("joint", "4/A", "2026-08-21"), content)[0]!;
expect(matchesInsiderOwner(entry, insiderReportedName(entry)!)).toBe(true);
expect(matchesInsiderOwner(entry, "B. OFFICER")).toBe(true);
const result = await report([{ filing: entry.filing, content }], insiderReportedName(entry)!);
expect(result.data.rows).toHaveLength(1);
expect(result.data.rows[0]).toMatchObject({ accessionNumber: "joint", status: "disclosure" });
});
test("an explanation-only amendment retains source text and owner filtering without inventing a missing transaction", async () => {
const amendedFiling = filing("explanation", "4/A", "2026-08-21");
const content = xml({ form: "4/A", original: "2026-08-20", noTransactions: true, note: "Corrects direct & indirect ownership; no transaction amounts added." });
const amended = parseInsiderFiling(amendedFiling, content);
expect(matchesInsiderOwner(amended[0]!, "a. officer")).toBe(true);
expect(buildInsiderRows(amended)[0]).toMatchObject({ status: "disclosure", insider: "A. OFFICER", shares: null, remarks: "Corrects direct & indirect ownership; no transaction amounts added.", footnotes: [{ id: "F1", text: "Corrects direct & indirect ownership; no transaction amounts added." }] });
const result = await report([{ filing: amendedFiling, content }, { filing: filing("original"), content: xml() }], "a. officer");
expect(result.data.complete).toBe(false);
expect(result.data.rows).toHaveLength(2);
expect(result.data.errors).toEqual(["explanation: Form 4/A is unreconciled; affected transaction totals are unavailable."]);
expect(result.text).toContain("Original filed 2026-08-20");
expect(result.text).toContain("Corrects direct & indirect ownership");
});
test("actual shared report keeps corrected amounts, source explanation and unrelated owner output", async () => {
const amendedFiling = filing("amendment", "4/A", "2026-08-21");
const entries = [{ filing: amendedFiling, content: xml({ form: "4/A", original: "2026-08-20", shares: 40, note: "Corrects 100 shares to 40 shares." }) },
{ filing: filing("original"), content: xml() },
{ filing: filing("independent"), content: xml({ owner: "B. OFFICER", cik: "222", shares: 500, security: "Class B" }) }];
const result = await report(entries);
expect(result.data.complete).toBe(false);
expect(result.data.rows[0]).toMatchObject({ form: "4/A", shares: 40, originalFilingDate: "2026-08-20", transactionFootnoteIds: ["F1"], remarks: "Corrects 100 shares to 40 shares." });
expect(result.text).toContain("Corrects 100 shares to 40 shares.");
expect(result.text).toContain("4/A");
const independentReport = await report(entries, "b. officer");
expect(independentReport.data.complete).toBe(true);
expect(independentReport.data.rows).toHaveLength(1);
expect(independentReport.data.rows[0]).toMatchObject({ insider: "B. OFFICER", amendmentStatus: null });
expect(independentReport.data.errors).toEqual([]);
});
test("failed amendment content still prevents trusting potentially affected originals", async () => {
const result = await report([{ filing: filing("amendment", "4/A", "2026-08-21"), content: null }, { filing: filing("original"), content: xml() }], "a. officer");
expect(result.data.complete).toBe(false);
expect(result.data.rows[0]).toMatchObject({ accessionNumber: "original", shares: 100, amendmentStatus: "potentially-amended" });
expect(result.data.errors).toHaveLength(2);
});
});