import { afterEach, expect, it, mock } from "bun:test"; import { promises as fs } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { createHash } from "node:crypto"; import { migrate } from "../tools/migrate-ki-datenschutz.mjs"; mock.module("@raycast/api", () => ({ environment: { supportPath: tmpdir() } })); const { restorePath, restoreText } = await import("../src/lib/velum-project"); const roots: string[] = []; afterEach(async () => { await Promise.all(roots.splice(0).map((root) => fs.rm(root, { recursive: true, force: true }))); }); it("migriert einen künstlichen Altbestand verlustarm ohne den Quellordner zu ändern", async () => { const root = await fs.mkdtemp(join(tmpdir(), "velum-legacy-test-")); roots.push(root); const project = join(root, "Projekt"); const targetSupport = join(root, "VelumSupport"); const oldSupport = join(root, "OldSupport"); const previewFile = join(root, "Migrationsvorschau.md"); await fs.mkdir(project); await fs.mkdir(join(oldSupport, "projects"), { recursive: true }); const legacyName = "KUNDE ALPHA.txt"; const legacyContent = "kundeAlphaService und KUNDE ALPHA"; await fs.writeFile(join(project, legacyName), legacyContent); await fs.writeFile(join(project, "Handbuch.docx"), "office"); await fs.writeFile(join(project, "leer.txt"), "Ohne Platzhalter"); const projectId = createHash("sha1").update(project).digest("hex").slice(0, 16); const manifestFile = join(oldSupport, "projects", `${projectId}.json`); await fs.writeFile(manifestFile, JSON.stringify({ version: 1, projectPath: project, projectTerms: [{ id: "t1", term: "Müller GmbH", category: "Kunde", enabled: true, aliases: [] }], variantTable: [ { termId: "t1", termVariant: "Müller GmbH", placeholderVariant: "KUNDE ALPHA", style: "typed", boundary: "strict" }, { termId: "t1", termVariant: "muellerGmbh", placeholderVariant: "kundeAlpha", style: "camel", boundary: "identifier" }, ], })); const preview = await migrate({ project, manifestFile, targetSupport, previewFile }); expect(preview.applied).toBe(false); expect(preview.summary.files).toBe(2); expect(preview.summary.skipped).toBe(1); const report = await fs.readFile(previewFile, "utf8"); expect(report).toContain('"KUNDE ALPHA.txt"'); expect(report).toContain('"KUNDE_100001.txt"'); expect(report).toContain('"kundeAlpha" → "KUNDE_100002" (1×)'); expect(report).toContain('"Handbuch.docx": Office- oder PDF-Datei'); expect(report).toContain("Keine Änderung."); expect(report).not.toContain("Müller GmbH"); expect((await fs.stat(previewFile)).mode & 0o777).toBe(0o600); expect(await fs.readFile(join(project, legacyName), "utf8")).toBe(legacyContent); expect(await fs.readdir(root)).not.toContain("VelumSupport"); await expect(migrate({ project, manifestFile, targetSupport, previewFile })).rejects.toMatchObject({ code: "EEXIST" }); await expect(migrate({ project, manifestFile, targetSupport, previewFile, apply: true })).rejects.toThrow(); await expect(migrate({ project, manifestFile, targetSupport, previewFile: join(project, "vorschau.md") })).rejects.toThrow(); const result = await migrate({ project, manifestFile, targetSupport, apply: true }); expect(result.applied).toBe(true); const run = JSON.parse(await fs.readFile(join(result.runDir!, "run.json"), "utf8")); const file = run.files[0]; const content = await fs.readFile(join(result.runDir!, "files", file.output), "utf8"); expect(restoreText(content, run.mapping, run.matchModes)).toBe("muellerGmbhService und Müller GmbH"); expect(restorePath(file.output, run.mapping, run.matchModes)).toBe("Müller GmbH.txt"); expect(await fs.readFile(join(project, legacyName), "utf8")).toBe(legacyContent); });