import { describe, it, expect } from "vitest"; import { normalizeLine, isExactItemMatch } from "@/lib/server/delivery-note-order-match"; describe("normalizeLine", () => { it("trims, lowercases, and collapses internal whitespace", () => { expect(normalizeLine(" Hex Bolt M20 ")).toBe("hex bolt m20"); }); it("returns empty string for blank input", () => { expect(normalizeLine(" ")).toBe(""); expect(normalizeLine("")).toBe(""); }); it("returns empty string for nullish input", () => { expect(normalizeLine(null as unknown as string)).toBe(""); expect(normalizeLine(undefined as unknown as string)).toBe(""); }); }); describe("isExactItemMatch", () => { it("matches when every line pairs 1:1 on normalized description and quantity", () => { const order = [ { description: "Hex Bolt M20", quantity: 50 }, { description: "Hex Nut M20", quantity: 50 }, ]; const dn = [ { description: "hex bolt m20", quantity: 50 }, { description: "HEX NUT M20", quantity: 50 }, ]; expect(isExactItemMatch(order, dn)).toBe(true); }); it("does not match when a quantity differs", () => { const order = [{ description: "Hex Bolt M20", quantity: 50 }]; const dn = [{ description: "Hex Bolt M20", quantity: 45 }]; expect(isExactItemMatch(order, dn)).toBe(false); }); it("does not match when the order has an extra line the DN doesn't have", () => { const order = [ { description: "Hex Bolt M20", quantity: 50 }, { description: "Washer M20", quantity: 50 }, ]; const dn = [{ description: "Hex Bolt M20", quantity: 50 }]; expect(isExactItemMatch(order, dn)).toBe(false); }); it("does not match when the DN has an extra line the order doesn't have", () => { const order = [{ description: "Hex Bolt M20", quantity: 50 }]; const dn = [ { description: "Hex Bolt M20", quantity: 50 }, { description: "Washer M20", quantity: 50 }, ]; expect(isExactItemMatch(order, dn)).toBe(false); }); it("requires matching multiplicity, not just membership, for duplicate lines", () => { // Order has the SAME line twice (two separate 50-unit lines of the // same bolt, e.g. from two different quote lines that priced // differently but described identically). The DN has only one. const order = [ { description: "Hex Bolt M20", quantity: 50 }, { description: "Hex Bolt M20", quantity: 50 }, ]; const dn = [{ description: "Hex Bolt M20", quantity: 50 }]; expect(isExactItemMatch(order, dn)).toBe(false); }); it("matches duplicate lines when both sides have the same multiplicity", () => { const order = [ { description: "Hex Bolt M20", quantity: 50 }, { description: "Hex Bolt M20", quantity: 50 }, ]; const dn = [ { description: "Hex Bolt M20", quantity: 50 }, { description: "Hex Bolt M20", quantity: 50 }, ]; expect(isExactItemMatch(order, dn)).toBe(true); }); it("returns false for two empty line sets rather than vacuously true", () => { // An order or DN with zero lines carries no signal — this must // never be treated as "everything matched." If this returned true, // an order with 0 lines could match every DN with 0 lines in the // company, which is meaningless. expect(isExactItemMatch([], [])).toBe(false); }); it("returns false when only the order is empty", () => { expect(isExactItemMatch([], [{ description: "X", quantity: 1 }])).toBe(false); }); it("returns false when only the DN is empty", () => { expect(isExactItemMatch([{ description: "X", quantity: 1 }], [])).toBe(false); }); it("treats descriptions that differ only in punctuation as non-matching", () => { // The "exact" is deliberate — operators do distinguish // M20x50 from M20-50, and treating them as the same would // mis-flag a one-off different product as a repeat. const order = [{ description: "M20x50 bolt", quantity: 1 }]; const dn = [{ description: "M20-50 bolt", quantity: 1 }]; expect(isExactItemMatch(order, dn)).toBe(false); }); it("matches when whitespace and case differ (normalization handles them)", () => { // The point of normalizeLine is exactly this case — operator // reorders with slight description drift on the PO side shouldn't // hide a real repeat. const order = [{ description: "Hex Bolt M20", quantity: 50 }]; const dn = [{ description: "hex bolt m20", quantity: 50 }]; expect(isExactItemMatch(order, dn)).toBe(true); }); });