Files
velum-raycast/tests/project-revision.test.ts
2026-09-25 10:31:45 +02:00

145 lines
7.8 KiB
TypeScript

import { afterEach, describe, expect, it, mock } from "bun:test";
import { promises as fs } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
mock.module("@raycast/api", () => ({ environment: { supportPath: tmpdir() } }));
const project = await import("../src/lib/velum-project");
const revision = await import("../src/lib/project-revision");
const roots: string[] = [];
afterEach(async () => { await Promise.all(roots.splice(0).map((root) => fs.rm(root, { recursive: true, force: true }))); });
describe("Nachbearbeitung von Projektständen", () => {
it("gibt eine Person frei und führt zwei andere nachträglich zusammen", async () => {
const root = await fs.mkdtemp(join(tmpdir(), "velum-revision-"));
roots.push(root);
const source = join(root, "Quelle");
const support = join(root, "Raycast");
await fs.mkdir(source);
const saved = await project.createRun(source, support);
saved.run.mapping = {
PERSON_01: { type: "PERSON", original: "Falsch Positiv" },
PERSON_02: { type: "PERSON", original: "Franz Fuchs" },
PERSON_03: { type: "PERSON", original: "Hr. Fuchs" },
PERSON_04: { type: "PERSON", original: "Franz F." },
};
saved.run.files = [{ source: "Bericht.txt", output: "PERSON_01-PERSON_03.txt", kind: "text" }];
await fs.writeFile(join(saved.directory, "files", "PERSON_01-PERSON_03.txt"),
"PERSON_01 traf PERSON_02 und PERSON_03. PERSON_04 blieb.");
await project.saveRun(saved.directory, saved.run);
const result = await revision.reviseProjectRun({
previous: saved.run, previousDirectory: saved.directory, support,
release: new Set(["PERSON_01"]), merges: { PERSON_03: "PERSON_02", PERSON_04: "PERSON_02" },
rewriteDocument: async () => { throw new Error("Unerwartetes Office-Dokument"); },
});
expect(result.run.files[0].output).toBe("Falsch Positiv-PERSON_02.txt");
expect(await fs.readFile(join(result.directory, "files", result.run.files[0].output), "utf8"))
.toBe("Falsch Positiv traf PERSON_02 und PERSON_02. PERSON_02 blieb.");
expect(result.run.mapping.PERSON_03.merged_into).toBe("PERSON_02");
expect(result.run.mapping.PERSON_04.merged_into).toBe("PERSON_02");
expect(result.run.mapping.PERSON_01).toBeUndefined();
expect(await project.loadProjectWhitelist(source, support))
.toEqual([{ type: "PERSON", original: "Falsch Positiv" }]);
expect(await fs.readFile(join(saved.directory, "files", "PERSON_01-PERSON_03.txt"), "utf8"))
.toContain("PERSON_03");
});
it("gibt eine konsolidierte Person samt Varianten frei", () => {
const plan = revision.planProjectRevision({
PERSON_01: { type: "PERSON", original: "Franz Fuchs" },
PERSON_02: { type: "PERSON", original: "Franz F.", merged_into: "PERSON_01" },
PERSON_03: { type: "PERSON", original: "Hr. Fuchs", merged_into: "PERSON_01" },
}, new Set(["PERSON_01"]), {});
expect(plan.released).toEqual(["PERSON_01", "PERSON_02", "PERSON_03"]);
expect(plan.whitelist.map((item) => item.original)).toEqual(["Franz Fuchs", "Franz F.", "Hr. Fuchs"]);
expect(plan.mapping).toEqual({});
});
it("ersetzt einen vorhandenen Projektordner nur, wenn er noch dem letzten Stand entspricht", async () => {
const root = await fs.mkdtemp(join(tmpdir(), "velum-revision-place-"));
roots.push(root);
const source = join(root, "Quelle");
const support = join(root, "Raycast");
await fs.mkdir(source);
const saved = await project.createRun(source, support);
saved.run.mapping = { PERSON_01: { type: "PERSON", original: "Falscher Treffer" } };
saved.run.files = [{ source: "Notiz.txt", output: "PERSON_01.txt", kind: "text" }];
saved.run.inPlace = true;
await fs.writeFile(join(saved.directory, "files", "PERSON_01.txt"), "PERSON_01");
await fs.writeFile(join(source, "PERSON_01.txt"), "PERSON_01");
await project.saveRun(saved.directory, saved.run);
await fs.writeFile(join(source, "PERSON_01.txt"), "PERSON_01 mit lokaler Änderung");
await expect(revision.reviseProjectRun({ previous: saved.run, previousDirectory: saved.directory,
release: new Set(["PERSON_01"]), merges: {}, support,
rewriteDocument: async () => { throw new Error("Unerwartetes Office-Dokument"); },
})).rejects.toThrow("seit dem letzten Stand geändert");
expect((await project.listRuns(source, support)).length).toBe(1);
expect(await fs.readFile(join(source, "PERSON_01.txt"), "utf8"))
.toBe("PERSON_01 mit lokaler Änderung");
await fs.writeFile(join(source, "PERSON_01.txt"), "PERSON_01");
const result = await revision.reviseProjectRun({ previous: saved.run, previousDirectory: saved.directory,
release: new Set(["PERSON_01"]), merges: {}, support,
rewriteDocument: async () => { throw new Error("Unerwartetes Office-Dokument"); },
});
expect(await fs.readFile(join(source, "Falscher Treffer.txt"), "utf8")).toBe("Falscher Treffer");
expect(await fs.readFile(join(result.directory, "backup", "PERSON_01.txt"), "utf8")).toBe("PERSON_01");
});
it("übergibt Office-Dateien mit einer gezielten Ersetzung an die Dokumentfunktion", async () => {
const root = await fs.mkdtemp(join(tmpdir(), "velum-revision-doc-"));
roots.push(root);
const source = join(root, "Quelle");
const support = join(root, "Raycast");
await fs.mkdir(source);
const saved = await project.createRun(source, support);
saved.run.mapping = {
PERSON_01: { type: "PERSON", original: "Falscher Treffer" },
PERSON_02: { type: "PERSON", original: "Echte Person" },
};
saved.run.files = [{ source: "Bericht.docx", output: "Bericht.docx", kind: "document" }];
await fs.writeFile(join(saved.directory, "files", "Bericht.docx"), "synthetic office bytes");
await project.saveRun(saved.directory, saved.run);
let called = false;
const result = await revision.reviseProjectRun({ previous: saved.run, previousDirectory: saved.directory,
release: new Set(["PERSON_01"]), merges: {}, support,
rewriteDocument: async (filename, data, mapping) => {
called = true;
expect(filename).toBe("Bericht.docx");
expect(data.toString()).toBe("synthetic office bytes");
expect(Object.keys(mapping)).toEqual(["PERSON_01"]);
return Buffer.from("changed office bytes");
},
});
expect(called).toBe(true);
expect(await fs.readFile(join(result.directory, "files", "Bericht.docx"), "utf8"))
.toBe("changed office bytes");
});
it("schützt längere importierte Platzhalter beim Freigeben eines Präfixes", async () => {
const root = await fs.mkdtemp(join(tmpdir(), "velum-revision-prefix-"));
roots.push(root);
const source = join(root, "Quelle");
const support = join(root, "Raycast");
await fs.mkdir(source);
const saved = await project.createRun(source, support);
saved.run.origin = "imported";
saved.run.mapping = {
BEGRIFF_100001: { type: "BEGRIFF", original: "Alpha" },
BEGRIFF_1000012: { type: "BEGRIFF", original: "Beta" },
};
saved.run.matchModes = { BEGRIFF_100001: "anywhere", BEGRIFF_1000012: "anywhere" };
saved.run.files = [{ source: "Code.txt", output: "BEGRIFF_1000012.txt", kind: "text" }];
await fs.writeFile(join(saved.directory, "files", "BEGRIFF_1000012.txt"),
"xBEGRIFF_1000012 und xBEGRIFF_100001");
await project.saveRun(saved.directory, saved.run);
const result = await revision.reviseProjectRun({ previous: saved.run, previousDirectory: saved.directory,
release: new Set(["BEGRIFF_100001"]), merges: {}, support,
rewriteDocument: async () => { throw new Error("Unerwartetes Office-Dokument"); },
});
expect(result.run.origin).toBe("imported");
expect(result.run.files[0].output).toBe("BEGRIFF_1000012.txt");
expect(await fs.readFile(join(result.directory, "files", result.run.files[0].output), "utf8"))
.toBe("xBEGRIFF_1000012 und xAlpha");
});
});