/*
RFC-0548: property-based test for the rfc-0548 migrator — verifies
idempotency (f(f(x)) == f(x)) and backup behavior.
- Do not test forge.agents.generate integration — that is tested separately.
- RFC-0548: initial PBT for rfc-0548 migrator.
*/
import { test, expect } from "vitest";
import fc from "fast-check";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { rfc0548Migrator } from "../migrators/rfc-0548.ts";
import type { SternsystemData, MigrationContext } from "../migrators/types.ts";
function makeTempDir(): string {
return fs.mkdtempSync(path.join(os.tmpdir(), "rfc-0548-pbt-"));
}
function makeCtx(): MigrationContext {
return {
systemId: "test-system",
missionId: "test-mission",
logger: { info: () => {} },
};
}
function makeData(rootPath: string): SternsystemData {
return { rootPath, dataPaths: [] };
}
test("rfc-0548 migrator is idempotent — f(f(x)) == f(x)", async () => {
await fc.assert(
fc.asyncProperty(fc.boolean(), fc.boolean(), async (hasAgentsMd, hasGeneratedMarker) => {
const dir = makeTempDir();
try {
if (hasAgentsMd) {
const content = hasGeneratedMarker
? "\n# Agent Guide\n"
: "# Hand-written Agent Guide\n";
fs.writeFileSync(path.join(dir, "AGENTS.md"), content, "utf8");
}
const data = makeData(dir);
const ctx = makeCtx();
const first = await rfc0548Migrator.transform(data, ctx);
const second = await rfc0548Migrator.transform(first, ctx);
// Both transforms should return the same data
expect(second.rootPath).toBe(first.rootPath);
expect(second.dataPaths).toEqual(first.dataPaths);
// Backup should exist if AGENTS.md existed
if (hasAgentsMd) {
expect(fs.existsSync(path.join(dir, "AGENTS.md.bak"))).toBe(true);
}
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
}),
{ numRuns: 50 },
);
});
test("rfc-0548 migrator creates backup when AGENTS.md exists", async () => {
const dir = makeTempDir();
try {
const content = "\n# Agent Guide\n";
fs.writeFileSync(path.join(dir, "AGENTS.md"), content, "utf8");
const data = makeData(dir);
const ctx = makeCtx();
await rfc0548Migrator.transform(data, ctx);
expect(fs.existsSync(path.join(dir, "AGENTS.md.bak"))).toBe(true);
const backup = fs.readFileSync(path.join(dir, "AGENTS.md.bak"), "utf8");
expect(backup).toBe(content);
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test("rfc-0548 migrator is a no-op when AGENTS.md does not exist", async () => {
const dir = makeTempDir();
try {
const data = makeData(dir);
const ctx = makeCtx();
const result = await rfc0548Migrator.transform(data, ctx);
expect(result.rootPath).toBe(dir);
expect(fs.existsSync(path.join(dir, "AGENTS.md.bak"))).toBe(false);
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test("rfc-0548 migrator backs up but does not modify hand-written AGENTS.md", async () => {
const dir = makeTempDir();
try {
const content = "# Hand-written Agent Guide\nNo generated marker here.\n";
fs.writeFileSync(path.join(dir, "AGENTS.md"), content, "utf8");
const data = makeData(dir);
const ctx = makeCtx();
await rfc0548Migrator.transform(data, ctx);
// Backup should exist
expect(fs.existsSync(path.join(dir, "AGENTS.md.bak"))).toBe(true);
// AGENTS.md should be unchanged
const after = fs.readFileSync(path.join(dir, "AGENTS.md"), "utf8");
expect(after).toBe(content);
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});