import { ADDIN_SIGNATURE_GUID, buildVersionHistoryXml, CUSTOM_XML_NAMESPACE, isTrackedDocument, readContentControlFields, readTrackedProperties, readVersionHistoryFromXml, stampTrackedProperties, writeVersionHistoryToXml, XmlVersionEntry, } from "./metadataService"; import { installWordMock } from "../test/wordMock"; describe("readTrackedProperties", () => { it("อ่านค่าได้ครบทั้ง 4 properties เมื่อมีทั้งหมด", async () => { installWordMock({ properties: { SavedByAddIn: ADDIN_SIGNATURE_GUID, DocumentTrackingId: "11111111-1111-1111-1111-111111111111", LastVersionId: "22222222-2222-2222-2222-222222222222", LastVersionTimestamp: "2026-08-19T10:00:00Z", }, }); await expect(readTrackedProperties()).resolves.toEqual({ savedByAddIn: ADDIN_SIGNATURE_GUID, documentTrackingId: "11111111-1111-1111-1111-111111111111", lastVersionId: "22222222-2222-2222-2222-222222222222", lastVersionTimestamp: "2026-08-19T10:00:00Z", }); }); it("property ที่ไม่มีในเอกสารเป็น undefined ไม่ throw", async () => { installWordMock({ properties: { UnrelatedProperty: "x" } }); const properties = await readTrackedProperties(); expect(properties.savedByAddIn).toBeUndefined(); expect(properties.documentTrackingId).toBeUndefined(); expect(properties.lastVersionId).toBeUndefined(); expect(properties.lastVersionTimestamp).toBeUndefined(); }); }); describe("isTrackedDocument (FR-1)", () => { it("true เมื่อ SavedByAddIn ตรง ADDIN_SIGNATURE_GUID", async () => { installWordMock({ properties: { SavedByAddIn: ADDIN_SIGNATURE_GUID } }); await expect(isTrackedDocument()).resolves.toBe(true); }); it("false เมื่อค่าไม่ตรง หรือไม่มี property", async () => { installWordMock({ properties: { SavedByAddIn: "OTHER-ADDIN-GUID" } }); await expect(isTrackedDocument()).resolves.toBe(false); installWordMock({ properties: {} }); await expect(isTrackedDocument()).resolves.toBe(false); }); }); describe("stampTrackedProperties (FR-2 ขั้นตอนที่ 2)", () => { it("stamp ครบ 4 properties ด้วย add() ทับค่าเดิมได้", async () => { const state = installWordMock({ properties: { SavedByAddIn: "OTHER-ADDIN-GUID", DocumentTrackingId: "old-tracking-id" }, }); await stampTrackedProperties({ documentTrackingId: "new-tracking-id", lastVersionId: "version-2", lastVersionTimestamp: "2026-08-19T11:00:00Z", }); expect(state.propertyAdds).toEqual([ { key: "SavedByAddIn", value: ADDIN_SIGNATURE_GUID }, { key: "DocumentTrackingId", value: "new-tracking-id" }, { key: "LastVersionId", value: "version-2" }, { key: "LastVersionTimestamp", value: "2026-08-19T11:00:00Z" }, ]); // add() ของ property ที่มีอยู่ = update ค่าในเอกสารจริง expect(state.properties.SavedByAddIn).toBe(ADDIN_SIGNATURE_GUID); expect(state.properties.DocumentTrackingId).toBe("new-tracking-id"); }); it("ไม่ส่งค่าใดมา = stamp เฉพาะ SavedByAddIn", async () => { const state = installWordMock({}); await stampTrackedProperties({}); expect(state.propertyAdds).toEqual([{ key: "SavedByAddIn", value: ADDIN_SIGNATURE_GUID }]); }); }); describe("Custom XML Part (delete-then-add, silent fail)", () => { const entries: XmlVersionEntry[] = [ { id: "version-3", number: 3, timestamp: "2026-08-19T12:00:00Z", user: "tester" }, { id: "version-2", number: 2, timestamp: "2026-08-19T11:00:00Z", user: "other" }, ]; it("writeVersionHistoryToXml ลบ part เดิมทั้งหมดก่อน add ของใหม่ แล้วคืน true", async () => { const state = installWordMock({ xmlParts: [ buildVersionHistoryXml([{ id: "version-1", number: 1, timestamp: "t", user: "u" }]), ], }); await expect(writeVersionHistoryToXml(entries)).resolves.toBe(true); // อันเดิมถูกลบ เหลืออันใหม่อันเดียว (อยู่ใน namespace ของเรา) expect(state.xmlParts).toHaveLength(1); expect(state.xmlParts[0].namespace).toBe(CUSTOM_XML_NAMESPACE); expect(state.xmlParts[0].xml).toContain('id="version-3"'); expect(state.xmlParts[0].xml).toContain('number="3"'); expect(state.xmlParts[0].xml).toContain('user="tester"'); }); it("เขียนไม่ได้ (known bug บน Mac) → คืน false ไม่ throw (AC-10)", async () => { installWordMock({ failXmlAdd: true }); await expect(writeVersionHistoryToXml(entries)).resolves.toBe(false); }); it("readVersionHistoryFromXml parse รายการและเรียงใหม่ → เก่า", async () => { installWordMock({ xmlParts: [ buildVersionHistoryXml([ { id: "version-1", number: 1, timestamp: "2026-08-19T09:00:00Z", user: "a" }, { id: "version-3", number: 3, timestamp: "2026-08-19T12:00:00Z", user: "b" }, { id: "version-2", number: 2, timestamp: "2026-08-19T11:00:00Z", user: "c" }, ]), ], }); const result = await readVersionHistoryFromXml(); expect(result.map((entry) => entry.number)).toEqual([3, 2, 1]); expect(result[0]).toEqual({ id: "version-3", number: 3, timestamp: "2026-08-19T12:00:00Z", user: "b", }); }); it("อ่าน fail → คืน [] ไม่ throw (XML part เป็น secondary เท่านั้น)", async () => { installWordMock({ failXmlRead: true }); await expect(readVersionHistoryFromXml()).resolves.toEqual([]); }); it("buildVersionHistoryXml escape อักขระพิเศษใน attribute", () => { const xml = buildVersionHistoryXml([ { id: 'a"b', number: 1, timestamp: "&", user: "u'ser" }, ]); expect(xml).toContain('id="a"b"'); expect(xml).toContain('timestamp="<t>&"'); expect(xml).toContain('user="u'ser"'); }); }); describe("readContentControlFields (FR-11)", () => { it("แปลง contentControls เป็น map tag → text", async () => { installWordMock({ contentControls: [ { tag: "employee_name", text: "สมชาย ใจดี" }, { tag: "salary", text: "50000" }, { tag: "start_date", text: "2026-08-01" }, ], }); await expect(readContentControlFields()).resolves.toEqual({ employee_name: "สมชาย ใจดี", salary: "50000", start_date: "2026-08-01", }); }); it("tag ซ้ำเอาตัวแรกตามลำดับในเอกสาร / tag ว่างข้าม", async () => { installWordMock({ contentControls: [ { tag: "position", text: "first" }, { tag: "position", text: "second" }, { tag: "", text: "ไม่มี tag" }, { tag: " ", text: "tag วรรคล้วน" }, { tag: "note", text: "ok" }, ], }); await expect(readContentControlFields()).resolves.toEqual({ position: "first", note: "ok", }); }); it("เอกสารไม่มี content controls → คืน {} ไม่ error", async () => { installWordMock({ contentControls: [] }); await expect(readContentControlFields()).resolves.toEqual({}); }); });