import assert from "node:assert/strict"; import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import path from "node:path"; import { test } from "node:test"; import { applyPlan, applyReplacements, assertEquivalentStorage, checkPolicy, createSnapshot, parseBaseline, parseCli, parseDate, parseHeaderHtml, parseReportHtml, readPlan, selectWorklist, validatePlan, } from "./ims.mjs"; const reportHtml = `
ProzessDocument OwnerRisk OwnerAsset OwnerKlassifizierungStatusGeprüft vonGeprüft amFreigegeben vonFreigegeben am
Alpha & Beta Kathi BorrmannRiskAssetInternPrüfung Oliver Krylow20.08.2026Release21.08.2026
Draft Oliver KrylowRiskAssetInternEntwurf
Old Oliver KrylowRiskAssetInternFreigegeben Oliver Krylow25.08.2025Release26.08.2025
`; test("parses IMS report rows and entities", () => { const rows = parseReportHtml(reportHtml); assert.equal(rows.length, 3); assert.deepEqual(rows[0], { id: "101", title: "Alpha & Beta", href: "/wiki/spaces/RD/pages/101/Alpha", area: "RD", documentOwner: "Kathi Borrmann", documentOwnerIds: ["owner-1"], riskOwner: "Risk", riskOwnerIds: [], assetOwner: "Asset", assetOwnerIds: [], classification: "Intern", status: "Prüfung", reviewer: "Oliver Krylow", reviewerIds: ["oliver"], reviewedAt: "20.08.2026", releasedBy: "Release", releasedAt: "21.08.2026", }); assert.equal(rows[1].id, "102"); }); test("builds distinct reviewer and owner worklists with RD first", () => { const rows = parseReportHtml(reportHtml); const reviewer = selectWorklist(rows, { mode: "reviewer", person: "ignored", accountId: "oliver", area: "all", today: "2026-08-27" }); assert.deepEqual(reviewer.candidates.map((row) => [row.id, row.reasons]), [["101", ["awaiting-review"]]]); const owner = selectWorklist(rows, { mode: "owner", person: "ignored", accountId: "oliver", area: "all", today: "2026-08-27" }); assert.deepEqual(owner.candidates.map((row) => row.id), ["103", "102"]); assert.deepEqual(owner.candidates[0].reasons, ["self-reviewer", "annual-review-due"]); assert.deepEqual(owner.candidates[1].reasons, ["draft", "missing-reviewer"]); const nearName = [{ ...rows[1], documentOwner: "Max Anna Meyer", documentOwnerIds: [] }]; assert.equal(selectWorklist(nearName, { mode: "owner", person: "Anna Meyer", area: "all", today: "2026-08-27" }).roleMatchCount, 0); }); test("rejects impossible German calendar dates", () => { assert.equal(parseDate("31.02.2026"), undefined); assert.equal(parseDate("2026-02-29"), undefined); assert.equal(parseDate("29.02.2024")?.toISOString(), "2024-02-29T00:00:00.000Z"); assert.equal(parseDate("2024-02-29")?.toISOString(), "2024-02-29T00:00:00.000Z"); }); test("parses the policy baseline and detects version changes", async () => { const baseline = parseBaseline(` - Instruction page: \`540606500\`, version \`24\`, [Instructions](https://site.example/wiki/x/a) - Announcement: \`1437532339\`, version \`1\`, [Announcement](https://site.example/wiki/x/b) - Worklist report: \`899153943\`, [Report](https://site.example/wiki/x/c) `); assert.equal(baseline.baseUrl, "https://site.example"); const client = { async get(requestPath) { return { version: { number: requestPath.includes("blogposts") ? 2 : 24 } }; }, }; const result = await checkPolicy(client, baseline); assert.equal(result.changed, true); assert.deepEqual(result.sources.map((source) => source.changed), [false, true]); }); test("extracts row and matrix IMS headers", () => { const rows = `
Status:Prüfung
Geprüft vonMarcus Storre
`; assert.deepEqual(parseHeaderHtml(rows), { Status: "Prüfung", "Geprüft von": "Marcus Storre" }); const matrix = `
Document OwnerRisk Owner
Oliver KrylowMarcus Storre
`; assert.deepEqual(parseHeaderHtml(matrix), { "Document Owner": "Oliver Krylow", "Risk Owner": "Marcus Storre" }); }); test("check-plan is read-only and apply requires the separate mutation gate", () => { assert.equal(parseCli(["check-plan", "plan.json"]).allowMutation, false); assert.throws(() => parseCli(["apply", "plan.json", "--expected-version", "7", "--plan-sha256", "a".repeat(64)]), /--allow-mutation/); assert.throws(() => parseCli(["apply", "plan.json", "--expected-version", "7", "--allow-mutation"]), /--plan-sha256/); }); test("locks the exact validated plan bytes", (t) => { const dir = mkdtempSync(path.join(tmpdir(), "ims-plan-test-")); t.after(() => rmSync(dir, { recursive: true, force: true })); const file = path.join(dir, "plan.json"); writeFileSync(file, JSON.stringify({ pageId: "123", expectedVersion: 7, operations: [{ kind: "page", versionComment: "geprüft", replacements: [{ from: "old", to: "new" }], expect: { contains: ["new"] } }], })); const first = readPlan(file); writeFileSync(file, `${first.source}\n`); const second = readPlan(file); assert.notEqual(first.sha256, second.sha256); assert.deepEqual(first.plan, second.plan); }); test("rejects plan and replacement shapes that could silently broaden a write", () => { assert.throws(() => validatePlan({ pageId: "x", expectedVersion: 1, operations: [] }), /pageId/); assert.throws(() => validatePlan({ pageId: "1", expectedVersion: 1, operations: [{ kind: "page", versionComment: "x", replacements: [] }] }), /lacks replacements/); assert.throws(() => validatePlan({ pageId: "1", expectedVersion: 1, operations: [{ kind: "page", versionComment: "x", replacements: [{ from: "a", to: "b" }], expect: { contains: "b" } }] }), /expect.contains/); assert.throws(() => validatePlan({ pageId: "1", expectedVersion: 1, operations: [{ kind: "page", versionComment: "x", replacements: [{ from: "a", to: "b" }] }] }), /at least one expectation/); assert.throws(() => validatePlan({ pageId: "1", expectedVersion: 1, operations: [ { kind: "page", versionComment: "x", replacements: [{ from: "a", to: "b" }], expect: { contains: ["b"] } }, { kind: "inline-comment", target: "b", text: "fix" }, ] }), /must precede/); assert.throws(() => applyReplacements("one old", [{ from: "old", to: "new", count: 2 }]), /found 1/); assert.throws( () => applyReplacements('old', [{ from: 'old', to: "new" }]), /inline-comment markers/, ); }); test("accepts server-only storage attributes but rejects semantic drift", () => { const expected = '

Hello

'; const normalized = '

Hello

'; assert.doesNotThrow(() => assertEquivalentStorage(expected, normalized)); assert.throws(() => assertEquivalentStorage(expected, normalized.replace("person", "other")), /differs/); assert.throws(() => assertEquivalentStorage("

Hello world

", "

Helloworld

"), /differs/); const links = '

One Two

'; const swapped = links.replace('ri:space-key="RD"', 'ri:space-key="TEMP"').replace('ri:space-key="GA"', 'ri:space-key="RD"').replace('ri:space-key="TEMP"', 'ri:space-key="GA"'); assert.throws(() => assertEquivalentStorage(links, swapped), /differs/); }); test("applies a version-gated page plan and emits an exact receipt", async () => { let page = { id: "123", title: "Process", status: "current", spaceId: "space-1", version: { number: 3, message: "before" }, body: { storage: { value: '
StatusPrüfung

Old

' } }, _links: { webui: "/wiki/spaces/RD/pages/123/Process" }, }; let mutations = 0; const client = { async get(requestPath) { assert.match(requestPath, /pages\/123\?body-format=storage/); return structuredClone(page); }, async mutate(method, requestPath, body) { mutations += 1; assert.equal(method, "PUT"); assert.equal(requestPath, "wiki/api/v2/pages/123"); assert.equal(body.spaceId, "space-1"); page = { ...page, version: structuredClone(body.version), body: { storage: { value: body.body.value.replace("

", '

') } }, }; return structuredClone(page); }, }; const plan = validatePlan({ pageId: "123", expectedVersion: 3, operations: [{ kind: "page", versionComment: "Rechtschreibkorrektur", replacements: [{ from: "Old", to: "New" }], expect: { contains: ["New"], notContains: ["Old"] }, }], }); const receipt = await applyPlan(plan, client, "https://site.example"); assert.equal(mutations, 1); assert.deepEqual(receipt, { pageId: "123", initialVersion: 3, operations: [{ kind: "page", version: 4, versionComment: "Rechtschreibkorrektur", validation: "passed" }], inlineComments: [], validation: "passed", url: "https://site.example/wiki/spaces/RD/pages/123/Process", finalVersion: 4, versionComment: "Rechtschreibkorrektur", header: { Status: "Prüfung" }, }); }); test("stops before mutation on version drift", async () => { let mutations = 0; const client = { async get() { return { id: "123", version: { number: 8 }, body: { storage: { value: "old" } } }; }, async mutate() { mutations += 1; }, }; const plan = validatePlan({ pageId: "123", expectedVersion: 7, operations: [{ kind: "page", versionComment: "x", replacements: [{ from: "old", to: "new" }], expect: { contains: ["new"] } }] }); await assert.rejects(applyPlan(plan, client, "https://site.example"), /version drift/); assert.equal(mutations, 0); }); test("creates and verifies an inline comment and its page marker", async () => { let storage = "

Before exact target after

"; const page = (representation) => ({ id: "123", title: "Process", status: "current", version: { number: 5, message: "before" }, body: representation === "storage" ? { storage: { value: storage } } : { atlas_doc_format: { value: JSON.stringify({ type: "doc", content: [{ type: "paragraph", content: [{ type: "text", text: "Before exact " }, { type: "text", text: "target after" }] }] }) } }, _links: { webui: "/wiki/pages/123" }, }); let comment; const client = { async get(requestPath) { if (requestPath.includes("inline-comments/c1")) return structuredClone(comment); if (requestPath.includes("/inline-comments?")) return { results: [{ id: "c1" }] }; if (requestPath.includes("atlas_doc_format")) return page("atlas_doc_format"); return page("storage"); }, async mutate(method, requestPath, body) { assert.equal(method, "POST"); assert.equal(requestPath, "wiki/api/v2/inline-comments"); assert.equal(body.inlineCommentProperties.textSelection, "exact target"); storage = storage.replace("exact target", 'exact target'); comment = { id: "c1", pageId: "123", resolutionStatus: "open", body: { storage: { value: "

Prüfung nötig

" } }, properties: { inlineOriginalSelection: "exact target", inlineMarkerRef: "marker-1" }, }; return { id: "c1" }; }, }; const plan = validatePlan({ pageId: "123", expectedVersion: 5, operations: [{ kind: "inline-comment", target: "exact target", text: "Prüfung nötig" }], }); const receipt = await applyPlan(plan, client, "https://site.example"); assert.equal(receipt.validation, "passed"); assert.deepEqual(receipt.inlineComments, [{ id: "c1", target: "exact target", markerRef: "marker-1", validation: "passed" }]); assert.match(storage, /inline-comment-marker/); }); test("rejects a snapshot changed during supplementary reads", async () => { let storageReads = 0; const client = { async get(requestPath) { if (requestPath.endsWith("?body-format=storage")) { storageReads += 1; return { id: "123", title: "Process", version: { number: storageReads === 1 ? 9 : 10 }, body: { storage: { value: "

Storage

" } } }; } if (requestPath.endsWith("?body-format=view")) return { id: "123", version: { number: 9 }, body: { view: { value: "

View

" } } }; return { results: [] }; }, }; await assert.rejects(createSnapshot(client, "123", "https://site.example"), /changed during snapshot/); }); test("captures one version-consistent page snapshot", async () => { const client = { async get(requestPath) { if (requestPath.endsWith("?body-format=storage")) return { id: "123", title: "Process", version: { number: 9, message: "x" }, body: { storage: { value: "

Storage

" } }, _links: { webui: "/wiki/pages/123" }, }; if (requestPath.endsWith("?body-format=view")) return { id: "123", version: { number: 9 }, body: { view: { value: "
StatusEntwurf
" } }, }; if (requestPath.includes("/labels")) return [{ results: [{ name: "ims-dokument" }] }]; if (requestPath.includes("/inline-comments")) return { results: [{ id: "i1" }] }; if (requestPath.includes("/footer-comments")) return { results: [{ id: "f1" }] }; if (requestPath.includes("/attachments")) return { results: [{ id: "a1" }] }; throw new Error(`Unexpected path: ${requestPath}`); }, }; const snapshot = await createSnapshot(client, "123", "https://site.example"); assert.equal(snapshot.url, "https://site.example/wiki/pages/123"); assert.deepEqual(snapshot.header, { Status: "Entwurf" }); assert.deepEqual(snapshot.labels, [{ name: "ims-dokument" }]); assert.equal(snapshot.body.storage, "

Storage

"); assert.match(snapshot.body.storageSha256, /^[0-9a-f]{64}$/); });