/*
RFC-0505: PBT test for the rfc-0505 migrator — verifies idempotency
(f(f(x)) == f(x)) by running the migrator twice and checking the result is
identical. Also verifies sidecar-to-claim-record transformation and sidecar deletion.
- Not a contract source of truth — the module under test owns the authoritative behavior; fixtures are minimal scaffolding only.
- RFC-0505: initial PBT test for rfc-0505 migrator idempotency.
*/
import { test, expect, beforeEach, afterEach } from "vitest";
import fs from "node:fs/promises";
import path from "node:path";
import os from "node:os";
import { rfc0505Migrator } from "./rfc-0505.ts";
import type { SternsystemData, MigrationContext } from "./types.ts";
function makeCtx(): MigrationContext {
return {
systemId: "test-system",
missionId: "test-mission",
logger: { info: () => {} },
};
}
function makeData(tmpDir: string): SternsystemData {
return { rootPath: tmpDir, dataPaths: [] };
}
let tmpDir: string;
beforeEach(async () => {
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "rfc-0505-pbt-"));
});
afterEach(async () => {
await fs.rm(tmpDir, { recursive: true, force: true });
});
test("rfc-0505 migrator is idempotent — f(f(x)) == f(x) with no sidecars", async () => {
const data = makeData(tmpDir);
const ctx = makeCtx();
await rfc0505Migrator.transform(data, ctx);
const firstDir = path.join(tmpDir, "src", "content", "surface", "claims");
const firstFiles = await fs.readdir(firstDir).catch(() => []);
await rfc0505Migrator.transform(data, ctx);
const secondDir = path.join(tmpDir, "src", "content", "surface", "claims");
const secondFiles = await fs.readdir(secondDir).catch(() => []);
expect(firstFiles.sort()).toEqual(secondFiles.sort());
});
test("rfc-0505 migrator transforms sidecars to claim records and deletes sidecars", async () => {
const data = makeData(tmpDir);
const ctx = makeCtx();
// Create a claim sidecar
const articlesDir = path.join(tmpDir, "src", "content", "surface", "articles", "de");
await fs.mkdir(articlesDir, { recursive: true });
const sidecarPath = path.join(articlesDir, "test-article.claims.yaml");
await fs.writeFile(
sidecarPath,
`claim-001:
provenance: manual
asOf: "2026-07-23"
sourceId: src-001
url: "https://example.com"
title: "Test Source"
`,
"utf-8",
);
await rfc0505Migrator.transform(data, ctx);
// Sidecar should be deleted
const sidecarExists = await fs
.stat(sidecarPath)
.then(() => true)
.catch(() => false);
expect(sidecarExists).toBe(false);
// Claim record should exist
const claimRecordPath = path.join(
tmpDir,
"src",
"content",
"surface",
"claims",
"de",
"claim-001.md",
);
const claimContent = await fs.readFile(claimRecordPath, "utf-8");
expect(claimContent).toContain("claimId: claim-001");
expect(claimContent).toContain("articleId: test-article");
expect(claimContent).toContain("reviewStatus: unverified");
});
test("rfc-0505 migrator is idempotent after transformation", async () => {
const data = makeData(tmpDir);
const ctx = makeCtx();
// Create a claim sidecar
const articlesDir = path.join(tmpDir, "src", "content", "surface", "articles", "de");
await fs.mkdir(articlesDir, { recursive: true });
await fs.writeFile(
path.join(articlesDir, "test-article.claims.yaml"),
`claim-001:
provenance: manual
asOf: "2026-07-23"
`,
"utf-8",
);
// First run transforms and deletes
await rfc0505Migrator.transform(data, ctx);
// Second run should be a no-op (no sidecars left)
await rfc0505Migrator.transform(data, ctx);
// Claim record should still exist
const claimRecordPath = path.join(
tmpDir,
"src",
"content",
"surface",
"claims",
"de",
"claim-001.md",
);
const claimContent = await fs.readFile(claimRecordPath, "utf-8");
expect(claimContent).toContain("claimId: claim-001");
});
test("rfc-0505 migrator does not overwrite existing claim records", async () => {
const data = makeData(tmpDir);
const ctx = makeCtx();
// Create a claim sidecar
const articlesDir = path.join(tmpDir, "src", "content", "surface", "articles", "de");
await fs.mkdir(articlesDir, { recursive: true });
await fs.writeFile(
path.join(articlesDir, "test-article.claims.yaml"),
`claim-001:
provenance: manual
asOf: "2026-07-23"
`,
"utf-8",
);
// Create an existing claim record
const claimsDir = path.join(tmpDir, "src", "content", "surface", "claims", "de");
await fs.mkdir(claimsDir, { recursive: true });
const customContent = `---
claimId: claim-001
articleId: test-article
claimText: "Custom claim text"
claimType: factual
sourceRefs: []
calculationInputs: []
limitations: []
verifiedAt: "2026-07-23"
reviewStatus: verified
---
`;
await fs.writeFile(path.join(claimsDir, "claim-001.md"), customContent, "utf-8");
await rfc0505Migrator.transform(data, ctx);
// Existing claim record should not be overwritten
const after = await fs.readFile(path.join(claimsDir, "claim-001.md"), "utf-8");
expect(after).toBe(customContent);
// Sidecar should still be deleted (transformation completed)
const sidecarExists = await fs
.stat(path.join(articlesDir, "test-article.claims.yaml"))
.then(() => true)
.catch(() => false);
expect(sidecarExists).toBe(false);
});