/*
RFC-0492: PBT test for the rfc-0492 industry dossier migrator — verifies
idempotency (f(f(x)) == f(x)) over the deprecated→new field copy transformation.
- Not a contract source of truth — the module under test owns the authoritative behavior; fixtures are minimal scaffolding only.
- RFC-0492: initial PBT test for industry dossier migrator.
*/
import { test, expect } from "vitest";
import fc from "fast-check";
import fs from "node:fs/promises";
import path from "node:path";
import os from "node:os";
import { rfc0492Migrator } from "./rfc-0492.ts";
import type { SternsystemData, MigrationContext } from "./types.ts";
const ctx: MigrationContext = {
systemId: "test",
missionId: "test-mission",
logger: { info: () => {} },
};
async function withTempWorkpiece(
industryFrontmatter: string,
fn: (dir: string) => Promise,
): Promise {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "rfc-0492-pbt-"));
const industriesDir = path.join(dir, "src", "content", "surface", "industries", "de");
await fs.mkdir(industriesDir, { recursive: true });
await fs.writeFile(path.join(industriesDir, "elektriker.md"), industryFrontmatter);
try {
await fn(dir);
} finally {
await fs.rm(dir, { recursive: true, force: true });
}
}
const stringListArbitrary = fc.array(fc.string({ minLength: 1, maxLength: 50 }), {
maxLength: 10,
});
const faqArbitrary = fc.array(
fc.record({
question: fc.string({ minLength: 1, maxLength: 100 }),
answer: fc.string({ minLength: 1, maxLength: 200 }),
}),
{ maxLength: 5 },
);
const industryArbitrary = fc.record({
name: fc.string({ minLength: 1, maxLength: 50 }),
slug: fc.constant("elektriker"),
proofSignals: stringListArbitrary,
faqs: faqArbitrary,
painPoints: stringListArbitrary,
hasNewFields: fc.boolean(),
});
test("rfc-0492 migrator is idempotent: f(f(x)) == f(x) for random industry records", async () => {
await fc.asyncProperty(industryArbitrary, async (industry) => {
const newFieldsBlock = industry.hasNewFields
? `trustSignals:\n - "already present"\nindustryFaq:\n - question: "existing"\n answer: "yes"\nevidenceRequirements:\n - "already here"\n`
: "";
const frontmatter = `---
name: "${industry.name}"
slug: ${industry.slug}
proofSignals:
${industry.proofSignals.map((s) => ` - "${s}"`).join("\n")}
faqs:
${industry.faqs.map((f) => ` - question: "${f.question}"\n answer: "${f.answer}"`).join("\n")}
painPoints:
${industry.painPoints.map((s) => ` - "${s}"`).join("\n")}
${newFieldsBlock}---
`;
await withTempWorkpiece(frontmatter, async (dir) => {
const data: SternsystemData = { rootPath: dir, dataPaths: [] };
const once = await rfc0492Migrator.transform(data, ctx);
const filePath = path.join(
dir,
"src",
"content",
"surface",
"industries",
"de",
"elektriker.md",
);
const content1 = await fs.readFile(filePath, "utf8");
const twice = await rfc0492Migrator.transform(once, ctx);
const content2 = await fs.readFile(filePath, "utf8");
expect(content2).toEqual(content1);
expect(twice).toEqual(once);
});
});
});
test("rfc-0492 migrator copies proofSignals → trustSignals when absent", async () => {
const frontmatter = `---
name: "Elektriker"
slug: elektriker
proofSignals:
- "Meisterbetrieb"
- "24h Notdienst"
faqs:
- question: "Was kostet eine Erstberatung?"
answer: "Die Erstberatung ist kostenlos."
painPoints:
- "Unzuverlässige Termine"
- "Unklare Preise"
---
`;
await withTempWorkpiece(frontmatter, async (dir) => {
const data: SternsystemData = { rootPath: dir, dataPaths: [] };
await rfc0492Migrator.transform(data, ctx);
const content = await fs.readFile(
path.join(dir, "src", "content", "surface", "industries", "de", "elektriker.md"),
"utf8",
);
expect(content).toContain("trustSignals:");
expect(content).toContain("Meisterbetrieb");
expect(content).toContain("industryFaq:");
expect(content).toContain("evidenceRequirements:");
expect(content).toContain("Unzuverlässige Termine");
});
});
test("rfc-0492 migrator does NOT overwrite existing new fields", async () => {
const frontmatter = `---
name: "Elektriker"
slug: elektriker
proofSignals:
- "old signal"
trustSignals:
- "new signal"
---
`;
await withTempWorkpiece(frontmatter, async (dir) => {
const data: SternsystemData = { rootPath: dir, dataPaths: [] };
await rfc0492Migrator.transform(data, ctx);
const content = await fs.readFile(
path.join(dir, "src", "content", "surface", "industries", "de", "elektriker.md"),
"utf8",
);
expect(content).toContain("new signal");
expect(content).toContain("trustSignals:");
expect(content).not.toMatch(/trustSignals:[\s\S]*old signal/);
});
});
test("rfc-0492 migrator does NOT set notdienst", async () => {
const frontmatter = `---
name: "Elektriker"
slug: elektriker
proofSignals:
- "signal"
---
`;
await withTempWorkpiece(frontmatter, async (dir) => {
const data: SternsystemData = { rootPath: dir, dataPaths: [] };
await rfc0492Migrator.transform(data, ctx);
const content = await fs.readFile(
path.join(dir, "src", "content", "surface", "industries", "de", "elektriker.md"),
"utf8",
);
expect(content).not.toContain("notdienst");
});
});