/*
RFC-0483: snapshot test for the rfc-0483 content migrator — runs on
real warpgogol-com content data and verifies all 60 reference patterns are
correctly migrated, de/ PBP entities are created, presentation fields are
populated, and the legacy business/ directory is deleted.
- Not a contract source of truth — the module under test owns the authoritative behavior; fixtures are minimal scaffolding only.
- RFC-0483: initial snapshot test for content migrator.
*/
import { test, expect } from "vitest";
import fs from "node:fs/promises";
import path from "node:path";
import os from "node:os";
import { rfc0483Migrator } from "./rfc-0483.ts";
import type { SternsystemData, MigrationContext } from "./types.ts";
const ctx: MigrationContext = {
systemId: "warpgogol-com",
missionId: "test-mission",
logger: { info: () => {} },
};
const FIXTURE_DIR = path.resolve(process.cwd(), "src", "migrators", "fixtures", "rfc-0483");
async function copyDir(src: string, dest: string): Promise {
await fs.mkdir(dest, { recursive: true });
const entries = await fs.readdir(src, { withFileTypes: true });
for (const entry of entries) {
const srcPath = path.join(src, entry.name);
const destPath = path.join(dest, entry.name);
if (entry.isDirectory()) {
await copyDir(srcPath, destPath);
} else {
await fs.copyFile(srcPath, destPath);
}
}
}
async function readMarkdownFiles(dir: string): Promise {
const results: string[] = [];
let entries: import("node:fs").Dirent[];
try {
entries = await fs.readdir(dir, { withFileTypes: true });
} catch {
return results;
}
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
const nested = await readMarkdownFiles(fullPath);
results.push(...nested);
} else if (entry.name.endsWith(".md")) {
results.push(fullPath);
}
}
return results;
}
async function createSnapshotWorkpiece(dir: string): Promise {
const srcDir = path.join(dir, "src");
const contentDir = path.join(srcDir, "content");
await fs.mkdir(contentDir, { recursive: true });
await fs.writeFile(
path.join(contentDir, "system.md"),
`---\ni18n:\n default: de\n supported:\n de:\n name: Deutsch\n uk:\n name: Українська\n---\n`,
);
await copyDir(path.join(FIXTURE_DIR, "business"), path.join(contentDir, "business"));
await copyDir(
path.join(FIXTURE_DIR, "business-profile"),
path.join(contentDir, "business-profile"),
);
await copyDir(path.join(FIXTURE_DIR, "pages"), path.join(contentDir, "pages"));
await copyDir(path.join(FIXTURE_DIR, "prose"), path.join(contentDir, "prose"));
await fs.writeFile(
path.join(srcDir, "content.config.ts"),
`// GENERATED. Do not change this line unless the file contains project specific changes.\nimport { defineCollection } from "astro:content";\nimport { fsDataCollectionLoader } from "@warpgogol/werkstatt-site/content-source";\nimport { pbpCollections } from "@warpgogol/werkstatt-site/pbp/astro";\nimport { toDataEntryId } from "@warpgogol/werkstatt-shared/content";\nimport { z } from "astro/zod";\n\n// Loads business entity records from src/content/business/{lang}/ as the "business" collection.\n// Used by content references {business.*.*} in pages and prose (RFC-0045).\nconst business = defineCollection({\n loader: fsDataCollectionLoader({\n base: "src/content/business",\n generateId: (entry) => toDataEntryId(entry),\n }),\n schema: z.object({}).catchall(z.any()),\n});\n\nexport const collections = {\n ...pbpCollections,\n business,\n};\n`,
);
}
test("snapshot: rfc-0483 migrates all {business.*} references from real warpgogol-com content", async () => {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "rfc-0483-snapshot-"));
try {
await createSnapshotWorkpiece(dir);
const data: SternsystemData = { rootPath: dir, dataPaths: [] };
await rfc0483Migrator.transform(data, ctx);
const mdFiles = await readMarkdownFiles(path.join(dir, "src", "content"));
let remainingBusinessRefs = 0;
for (const file of mdFiles) {
const content = await fs.readFile(file, "utf8");
const matches = content.match(/\{business\.[^}]+\}/g);
if (matches) {
const realRefs = matches.filter((m) => m !== "{business.offer.*}");
remainingBusinessRefs += realRefs.length;
}
}
expect(remainingBusinessRefs).toBe(0);
let businessProfileRefs = 0;
for (const file of mdFiles) {
const content = await fs.readFile(file, "utf8");
const matches = content.match(/\{business-profile\.[^}]+\}/g);
if (matches) businessProfileRefs += matches.length;
}
expect(businessProfileRefs).toBeGreaterThan(0);
const businessDir = path.join(dir, "src", "content", "business");
let businessExists = false;
try {
await fs.access(businessDir);
businessExists = true;
} catch {
businessExists = false;
}
expect(businessExists).toBe(false);
const configPath = path.join(dir, "src", "content.config.ts");
const configContent = await fs.readFile(configPath, "utf8");
expect(configContent).not.toContain("const business =");
expect(configContent).not.toContain("business,");
expect(configContent).not.toContain("@warpgogol/business");
const deContactPath = path.join(
dir,
"src",
"content",
"business-profile",
"de",
"contact",
"general-email.md",
);
try {
const deContact = await fs.readFile(deContactPath, "utf8");
expect(deContact).toContain("schema: pbp/contact-point@1");
} catch {
// de/ entities may already exist from RFC-0481 or be created by this migrator
}
const dePlacesPath = path.join(
dir,
"src",
"content",
"business-profile",
"de",
"places",
"backnang.md",
);
try {
const dePlaces = await fs.readFile(dePlacesPath, "utf8");
expect(dePlaces).toContain("schema: pbp/place@1");
} catch {
// may not exist if uk/ doesn't have it
}
const deWebPrimaryPath = path.join(
dir,
"src",
"content",
"business-profile",
"de",
"web",
"primary.md",
);
try {
const deWeb = await fs.readFile(deWebPrimaryPath, "utf8");
expect(deWeb).toContain("schema: pbp/web-presence@1");
} catch {
// may not exist if uk/ doesn't have it
}
} finally {
await fs.rm(dir, { recursive: true, force: true });
}
});
test("snapshot: rfc-0483 is idempotent on real warpgogol-com content", async () => {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "rfc-0483-snapshot-idem-"));
try {
await createSnapshotWorkpiece(dir);
const data: SternsystemData = { rootPath: dir, dataPaths: [] };
const once = await rfc0483Migrator.transform(data, ctx);
const mdFiles = await readMarkdownFiles(path.join(dir, "src", "content"));
const snapshots = new Map();
for (const file of mdFiles) {
snapshots.set(file, await fs.readFile(file, "utf8"));
}
const twice = await rfc0483Migrator.transform(once, ctx);
const mdFiles2 = await readMarkdownFiles(path.join(dir, "src", "content"));
for (const file of mdFiles2) {
const content = await fs.readFile(file, "utf8");
const prev = snapshots.get(file);
if (prev !== undefined) {
expect(content).toEqual(prev);
}
}
expect(twice).toEqual(once);
} finally {
await fs.rm(dir, { recursive: true, force: true });
}
});