import { describe, expect, it } from "vitest"; import { detectKindFromText, detectKindFromFile, detectedKindToParsedBatchKind, } from "./x12-detect"; // Minimal X12 envelopes — just enough to exercise the ST01 match. // Real fixtures live in backend/tests/fixtures; we use synthetic ones // here because the frontend doesn't need the full file content, just // the first ~4KB. const ISA_837P = ( "ISA*00* *00* *ZZ*SENDER *ZZ*RECEIVER " + "*260617*1937*^*00501*000000001*0*P*:~" + "GS*HC*SENDER*RECEIVER*20260706*1937*1*X*005010X222A1~" + "ST*837*0001*005010X222A1~" ); const ISA_835 = ( "ISA*00* *00* *ZZ*SENDER *ZZ*RECEIVER " + "*260617*1937*^*00501*000000001*0*P*:~" + "GS*HP*SENDER*RECEIVER*20260706*1937*1*X*005010X221A1~" + "ST*835*0001~" ); const ISA_999 = ( "ISA*00* *00* *ZZ*SENDER *ZZ*RECEIVER " + "*260617*1937*^*00501*000000001*0*P*:~" + "GS*FA*SENDER*RECEIVER*20260706*1937*1*X*005010X231A1~" + "ST*999*0001~" ); const ISA_277CA = ( "ISA*00* *00* *ZZ*SENDER *ZZ*RECEIVER " + "*260617*1937*^*00501*000000001*0*P*:~" + "GS*HN*SENDER*RECEIVER*20260706*1937*1*X*005010X214~" + "ST*277*0001*005010X214~" ); const ISA_277CA_QUALIFIED = ( "ISA*00* *00* *ZZ*SENDER *ZZ*RECEIVER " + "*260617*1937*^*00501*000000001*0*P*:~" + "GS*HN*SENDER*RECEIVER*20260706*1937*1*X*005010X214~" + "ST*277CA*0001*005010X214~" ); const TA1_FILE = ( "ISA*00* *00* *ZZ*SENDER *ZZ*RECEIVER " + "*260520*1750*^*00501*000000001*0*P*:~" + "TA1*000000001*20260520*1750*A*000*20260520~" + "IEA*1*000000001~" ); describe("detectKindFromText", () => { it("detects ST*837* as 837p", () => { expect(detectKindFromText(ISA_837P)).toBe("837p"); }); it("detects ST*837P* as 837p (professional qualifier)", () => { const withQualifier = ISA_837P.replace("ST*837*", "ST*837P*"); expect(detectKindFromText(withQualifier)).toBe("837p"); }); it("detects ST*835* as 835", () => { expect(detectKindFromText(ISA_835)).toBe("835"); }); it("detects ST*999* as 999", () => { expect(detectKindFromText(ISA_999)).toBe("999"); }); it("detects ST*277* as 277ca", () => { expect(detectKindFromText(ISA_277CA)).toBe("277ca"); }); it("detects ST*277CA* as 277ca (qualified form)", () => { expect(detectKindFromText(ISA_277CA_QUALIFIED)).toBe("277ca"); }); it("detects TA1 envelope as ta1 (no ST, bare TA1 segment)", () => { expect(detectKindFromText(TA1_FILE)).toBe("ta1"); }); it("returns unknown for garbage input", () => { expect(detectKindFromText("not edi at all")).toBe("unknown"); }); it("returns unknown for an empty string", () => { expect(detectKindFromText("")).toBe("unknown"); }); it("is case-insensitive on ST segment", () => { // Real X12 is uppercase, but be lenient — files from various tools // sometimes have mixed case. expect(detectKindFromText(ISA_835.toLowerCase())).toBe("835"); }); it("does not false-positive on ST*8370 (5 chars after ST*)", () => { // Catches a regression where the matcher would substring-match too // eagerly (e.g. matching ST*837 against ST*8370*). The require is // that the char after the digits is `*`, not another digit. const noise = "ISA*00*...*ST*8370*0001~"; expect(detectKindFromText(noise)).toBe("unknown"); }); }); describe("detectedKindToParsedBatchKind", () => { it("maps 837p → 837p", () => { expect(detectedKindToParsedBatchKind("837p")).toBe("837p"); }); it("maps 835 → 835", () => { expect(detectedKindToParsedBatchKind("835")).toBe("835"); }); it("returns null for 999 (Upload page doesn't ingest 999s)", () => { expect(detectedKindToParsedBatchKind("999")).toBeNull(); }); it("returns null for 277ca", () => { expect(detectedKindToParsedBatchKind("277ca")).toBeNull(); }); it("returns null for ta1", () => { expect(detectedKindToParsedBatchKind("ta1")).toBeNull(); }); it("returns null for unknown", () => { expect(detectedKindToParsedBatchKind("unknown")).toBeNull(); }); }); describe("detectKindFromFile", () => { it("detects 837p from a File blob", async () => { const file = new File([ISA_837P], "test.x12", { type: "text/plain" }); expect(await detectKindFromFile(file)).toBe("837p"); }); it("detects 835 from a File blob (the original SP35 repro)", async () => { // The exact scenario the user hit: drop an 835 file on the Upload // page while the dropdown still says "837p". The helper must // return "835" so the UI can switch the dropdown before the user // hits Parse. const file = new File([ISA_835], "tp11525703-835.x12", { type: "text/plain" }); expect(await detectKindFromFile(file)).toBe("835"); }); it("returns unknown on an empty file (no crash)", async () => { const file = new File([""], "empty.x12", { type: "text/plain" }); expect(await detectKindFromFile(file)).toBe("unknown"); }); });