import assert from "node:assert/strict"; import { test } from "node:test"; import { blfsEqual, parseBlfs, serializeBlfs } from "./blf-xml"; import { addAlwaysIncludedColleagues, isEligibleColleague, primaryDepartmentName, splitPeerName } from "./department"; import { mergeDepartmentBlfs } from "./merge"; import { resolveTemplateId, SLOT_CONTRACT } from "./slot-contract"; const colleagues = [ { id: 29, number: "100", firstName: "Adam", lastName: "Moussa" }, { id: 33, number: "111", firstName: "Alyssa", lastName: "Ficarra" }, { id: 38, number: "114", firstName: "Ashley", lastName: "Fedner" }, { id: 66, number: "115", firstName: "Derrick", lastName: "Smith" }, { id: 68, number: "113", firstName: "Sarah", lastName: "May" }, { id: 69, number: "116", firstName: "Cindy", lastName: "Vallecillo" }, ]; test("resolveTemplateId matches longest Sea Haven template first", () => { assert.equal( resolveTemplateId("yealinkT54W-door-unlock-with-sp.ph.xml"), "t54w-door-unlock-with-sp" ); assert.equal( resolveTemplateId("yealinkT57W-door-unlock-with-sp.ph.xml"), "t57w-door-unlock-with-sp" ); assert.equal(resolveTemplateId("yealinkT54W-door-unlock.ph.xml"), "t54w-door-unlock"); assert.equal(resolveTemplateId("yealinkT58W-door-unlock.ph.xml"), "t58w-door-unlock"); assert.equal(resolveTemplateId("yealinkT54W.ph.xml"), undefined); assert.equal(resolveTemplateId("yealinkT57W.ph.xml"), undefined); }); test("T57W+SP slot contract matches T54W+SP", () => { assert.deepEqual( SLOT_CONTRACT["t57w-door-unlock-with-sp"], SLOT_CONTRACT["t54w-door-unlock-with-sp"] ); }); test("parseBlfs reads Line self-close and extension BLFs", () => { const xml = '112'; assert.deepEqual(parseBlfs(xml), [ { id: "-1", blfNo: 1, blfType: "Line", blfTypeId: "6", value: "" }, { id: "91", blfNo: 6, blfType: "BLF", blfTypeId: "0", value: "112" }, ]); }); test("merge skips self and sorts by extension", () => { const result = mergeDepartmentBlfs({ currentXml: "", selfExtension: "114", colleagues, contract: SLOT_CONTRACT["t54w-door-unlock-with-sp"], }); const managed = parseBlfs(result.xml).filter((e) => e.blfNo >= 6); assert.deepEqual( managed.map((e) => e.value), ["100", "111", "113", "115", "116"] ); assert.equal(result.overflow, 0); assert.equal(result.placed, 5); assert.equal(managed.some((e) => e.value === "114"), false); }); test("merge never writes reserved T54W+SP unlock key 2", () => { const current = '111112114'; const result = mergeDepartmentBlfs({ currentXml: current, selfExtension: "113", colleagues, contract: SLOT_CONTRACT["t54w-door-unlock-with-sp"], }); const entries = parseBlfs(result.xml); assert.equal(entries.some((e) => e.blfNo === 2), false); assert.equal(entries.find((e) => e.blfNo === 1)?.blfType, "Line"); }); test("merge writes SP1-SP3 on T54W+SP keys 3-5", () => { const result = mergeDepartmentBlfs({ currentXml: "", selfExtension: "111", colleagues, contract: SLOT_CONTRACT["t54w-door-unlock-with-sp"], }); const parking = parseBlfs(result.xml).filter((e) => e.blfNo >= 3 && e.blfNo <= 5); assert.deepEqual( parking.map((e) => ({ blfNo: e.blfNo, type: e.blfType, value: e.value })), [ { blfNo: 3, type: "SharedParking", value: "SP1" }, { blfNo: 4, type: "SharedParking", value: "SP2" }, { blfNo: 5, type: "SharedParking", value: "SP3" }, ] ); }); test("merge preserves personal BLFs outside the managed range", () => { const current = '215'; const result = mergeDepartmentBlfs({ currentXml: current, selfExtension: "116", colleagues, contract: SLOT_CONTRACT["t54w-door-unlock-with-sp"], }); const personal = parseBlfs(result.xml).find((e) => e.blfNo === 16); assert.equal(personal?.value, "215"); assert.equal(personal?.id, "9"); }); test("merge caps overflow at the managed slot count", () => { const many = Array.from({ length: 12 }, (_, i) => ({ id: 200 + i, number: String(300 + i), firstName: "User", lastName: `Z${String(i).padStart(2, "0")}`, })); const result = mergeDepartmentBlfs({ currentXml: "", selfExtension: "111", colleagues: many, contract: SLOT_CONTRACT["t54w-door-unlock"], }); assert.equal(result.placed, 10); assert.equal(result.overflow, 2); const managed = parseBlfs(result.xml).filter((e) => e.blfNo >= 3 && e.blfNo <= 12); assert.equal(managed.length, 10); assert.equal(managed[0]?.blfNo, 3); assert.equal(managed[9]?.blfNo, 12); }); test("merge is a no-op when XML already matches", () => { const first = mergeDepartmentBlfs({ currentXml: "", selfExtension: "116", colleagues, contract: SLOT_CONTRACT["t54w-door-unlock-with-sp"], }); const second = mergeDepartmentBlfs({ currentXml: first.xml, selfExtension: "116", colleagues, contract: SLOT_CONTRACT["t54w-door-unlock-with-sp"], }); assert.equal(first.changed, true); assert.equal(second.changed, false); assert.equal(blfsEqual(first.xml, second.xml), true); }); test("merge keeps assigned shared-parking IDs so later runs are a no-op", () => { const first = mergeDepartmentBlfs({ currentXml: "", selfExtension: "116", colleagues, contract: SLOT_CONTRACT["t54w-door-unlock-with-sp"], }); const assigned = serializeBlfs( parseBlfs(first.xml).map((entry) => entry.blfType === "SharedParking" || entry.blfType === "Line" ? { ...entry, id: String(900 + entry.blfNo) } : entry ) ); const second = mergeDepartmentBlfs({ currentXml: assigned, selfExtension: "116", colleagues, contract: SLOT_CONTRACT["t54w-door-unlock-with-sp"], }); assert.equal(second.changed, false); const parking = parseBlfs(second.xml).filter((e) => e.blfNo >= 3 && e.blfNo <= 5); assert.deepEqual( parking.map((e) => e.id), ["903", "904", "905"] ); }); test("serializeBlfs normalizes empty lists", () => { assert.equal(serializeBlfs([]), ""); assert.equal(blfsEqual("", serializeBlfs([])), true); }); test("primaryDepartmentName prefers Office over DEFAULT", () => { assert.equal( primaryDepartmentName({ Id: 1, Number: "111", Groups: [{ Id: 28, Name: "DEFAULT" }, { Id: 142, Name: "Office" }], }), "Office" ); assert.equal( primaryDepartmentName({ Id: 2, Number: "201", Groups: [{ Id: 28, Name: "DEFAULT" }], }), undefined ); }); test("addAlwaysIncludedColleagues injects ext 100 when missing", () => { const with100 = addAlwaysIncludedColleagues(colleagues, [ { id: 29, number: "100", firstName: "Adam", lastName: "Moussa" }, ]); assert.equal(with100.some((c) => c.number === "100"), true); const again = addAlwaysIncludedColleagues(with100, [ { id: 29, number: "100", firstName: "Adam", lastName: "Moussa" }, ]); assert.equal(again.filter((c) => c.number === "100").length, 1); }); test("splitPeerName uses last token as last name", () => { assert.deepEqual(splitPeerName("Adam Moussa"), { firstName: "Adam", lastName: "Moussa" }); }); test("isEligibleColleague skips queues and voicemail", () => { assert.equal(isEligibleColleague({ Id: 1, Number: "111", FirstName: "A", LastName: "B" }), true); assert.equal(isEligibleColleague({ Id: 2, Number: "801", FirstName: "After", LastName: "Hours" }), false); assert.equal(isEligibleColleague({ Id: 3, Number: "201", FirstName: "Voicemail" }), false); assert.equal(isEligibleColleague({ Id: 4, Number: "scheduler" }), false); });