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, plainTextFromHtml, readPlan, selectWorklist, validatePlan, } from "./ims.mjs"; const reportHtml = `
| Prozess | Document Owner | Risk Owner | Asset Owner | Klassifizierung | Status | Geprüft von | Geprüft am | Freigegeben von | Freigegeben am |
|---|---|---|---|---|---|---|---|---|---|
| Alpha & Beta | Kathi Borrmann | Risk | Asset | Intern | Prüfung | Oliver Krylow | 20.08.2026 | Release | 21.08.2026 |
| Draft | Oliver Krylow | Risk | Asset | Intern | Entwurf | ||||
| Old | Oliver Krylow | Risk | Asset | Intern | Freigegeben | Oliver Krylow | 25.08.2025 | Release | 26.08.2025 |
Seite ‚Continuous Integration‘ verlinken
"), "Seite ‚Continuous Integration‘ verlinken"); assert.equal(plainTextFromHtml("01.–04.09.2026
"), "01.–04.09.2026"); }); 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 von | Marcus Storre |
| Document Owner | Risk Owner |
|---|---|
| Oliver Krylow | Marcus Storre |
new
", replacements: [{ from: "x", to: "y" }], expect: { contains: ["new"] } }] }), /invalid empty-page initialization/); assert.throws(() => validatePlan({ pageId: "1", expectedVersion: 1, operations: [{ kind: "add-label", name: "ims-dokument", expectedBefore: [] }] }), /must add ims-dokument/); 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('Hello world
", "Helloworld
"), /differs/); const links = '| Status | Prüfung |
|---|
| Document Owner | Status |
|---|---|
| Oliver Krylow | Prüfung |
", '
') } },
};
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: { "Document Owner": "Oliver Krylow", Status: "Prüfung" },
});
});
test("bootstraps only an empty IMS page, validates its rendered header, then adds its label", async () => {
const header = {
"Document Owner": "Oliver Krylow", "Risk Owner": "Steffen Kröger", "Asset Owner": "Steffen Kröger",
Klassifizierung: "Vertraulich", "Geprüft von": "Normen Rachel", "Geprüft am": "",
"Freigegeben von": "Steffen Kröger", "Freigegeben am": "", Status: "Entwurf",
};
const storage = 'Document Owner Risk Owner Asset Owner Klassifizierung Oliver Krylow Steffen Kröger Steffen Kröger Vertraulich Geprüft von Geprüft am Freigegeben von Freigegeben am Status Normen Rachel Steffen Kröger Entwurf
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"); if (requestPath.includes("body-format=view")) return { id: "123", version: { number: 5 }, body: { view: { value: "Before exact target after
" } } }; 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", '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: "/spaces/RD/pages/123", base: "https://site.example/wiki" }, }; if (requestPath.endsWith("?body-format=view")) return { id: "123", version: { number: 9 }, body: { view: { value: "| Status | Entwurf |
|---|
Storage
"); assert.match(snapshot.body.storageSha256, /^[0-9a-f]{64}$/); });