import { normalizeStructuredCv, parseStructuredCvJson } from "./profileCv"; const keyedItems = [ ["jobs", { id: "job:recorded-Æ_01", title: "Engineer", bullets: ["Recorded delivery"], skills: ["C#"] }], ["education", { id: "education/server:key-02", qualification: "Recorded degree", details: ["Recorded distinction"] }], ["certifications", { id: "certification:key-03", name: "Recorded certification", details: ["Recorded detail"] }], ["projects", { id: "project:key-04", name: "Recorded project", bullets: ["Recorded outcome"], skills: ["SQL"] }], ] as const; test.each(keyedItems)("preserves the server item ID when normalizing %s", (collection, item) => { const profile = normalizeStructuredCv({ [collection]: [item] }); expect(profile[collection]).toHaveLength(1); expect(profile[collection][0]).toMatchObject(item); expect(parseStructuredCvJson(JSON.stringify(profile))[collection][0]).toMatchObject(item); }); test("legacy structured items without IDs remain usable and acquire no client-generated IDs", () => { const legacy = Object.fromEntries(keyedItems.map(([collection, item]) => { const { id: _id, ...fields } = item; return [collection, [fields]]; })); const profile = normalizeStructuredCv(legacy); const serialized = JSON.parse(JSON.stringify(profile)); for (const [collection] of keyedItems) { expect(profile[collection]).toHaveLength(1); expect(serialized[collection][0]).toMatchObject(legacy[collection][0]); expect(serialized[collection][0]).not.toHaveProperty("id"); } }); test("legacy parsed sections keep their facts without inventing structured item IDs", () => { const profile = parseStructuredCvJson(JSON.stringify([ { name: "Professional Summary", content: "Recorded delivery\nRecorded support", wordCount: 4 }, { name: "Skills", content: "C#, SQL", wordCount: 2 }, { name: "Languages", content: "Norwegian: Native", wordCount: 2 }, ])); expect(profile.summary).toEqual(["Recorded delivery", "Recorded support"]); expect(profile.skills).toEqual(["C#", "SQL"]); expect(profile.languages).toEqual([{ name: "Norwegian", level: "Native", notes: undefined }]); for (const [collection] of keyedItems) expect(profile[collection]).toEqual([]); }); test("API null and missing numeric metadata remain absent rather than becoming zero", () => { const inputs = [ { metadata: { profileVersion: null, appliedExtractionRunId: null, fields: { "contact.fullName": { confidence: null, sourcePage: null } } } }, { metadata: { fields: { "contact.fullName": {} } } }, ]; for (const input of inputs) { for (const profile of [normalizeStructuredCv(input), parseStructuredCvJson(JSON.stringify(input))]) { expect(profile.metadata.profileVersion).toBeUndefined(); expect(profile.metadata.appliedExtractionRunId).toBeUndefined(); expect(profile.metadata.fields["contact.fullName"].confidence).toBeUndefined(); expect(profile.metadata.fields["contact.fullName"].sourcePage).toBeUndefined(); const serialized = JSON.parse(JSON.stringify(profile)); expect(serialized.metadata).not.toHaveProperty("profileVersion"); expect(serialized.metadata).not.toHaveProperty("appliedExtractionRunId"); expect(serialized.metadata.fields["contact.fullName"]).not.toHaveProperty("confidence"); expect(serialized.metadata.fields["contact.fullName"]).not.toHaveProperty("sourcePage"); } } }); test("zero, finite numbers and numeric strings keep the established metadata number conversion", () => { for (const values of [[0, 0, 0, 0], [5, 3, 0.8, 2], ["6", "4", "0.65", "3"]]) { const [profileVersion, appliedExtractionRunId, confidence, sourcePage] = values; const input = { metadata: { profileVersion, appliedExtractionRunId, fields: { "contact.fullName": { confidence, sourcePage } } } }; for (const profile of [normalizeStructuredCv(input), parseStructuredCvJson(JSON.stringify(input))]) { expect(profile.metadata.profileVersion).toBe(Number(profileVersion)); expect(profile.metadata.appliedExtractionRunId).toBe(Number(appliedExtractionRunId)); expect(profile.metadata.fields["contact.fullName"].confidence).toBe(Number(confidence)); expect(profile.metadata.fields["contact.fullName"].sourcePage).toBe(Number(sourcePage)); } } });