/*
RFC-0508: PBT test for the rfc-0508 migrator — verifies idempotency
(f(f(x)) == f(x)) and that Participant fields are correctly added to Person records.
- Not a contract source of truth — the module under test owns the authoritative behavior; fixtures are minimal scaffolding only.
- RFC-0508: initial PBT test for rfc-0508 migrator idempotency and field addition.
*/
import { test, expect } from "vitest";
import fs from "node:fs/promises";
import path from "node:path";
import os from "node:os";
import { rfc0508Migrator } from "./rfc-0508.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: [],
};
}
const SAMPLE_PERSON = `---
slug: "test-person"
name: "Test Person"
role: "Developer"
photo: test-portrait
affiliations: [founder]
location: "Berlin"
lifespan:
born: 1980
sameAs:
- "https://linkedin.com/in/test"
statement: "A test person."
stats:
- label: "Years"
value: "10"
cta:
label: "Contact"
target: contact
page:
enabled: true
order: 1
bio: |
Test bio.
---
`;
const SAMPLE_PERSON_NO_PAGE = `---
slug: "private-person"
name: "Private Person"
affiliations: [team]
page:
enabled: false
order: 2
bio: |
Private bio.
---
`;
test("rfc-0508 migrator is idempotent — f(f(x)) == f(x)", async () => {
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "rfc-0508-pbt-"));
const data = makeData(tmpDir);
const ctx = makeCtx();
try {
const peopleDir = path.join(tmpDir, "src", "content", "people", "de");
await fs.mkdir(peopleDir, { recursive: true });
await fs.writeFile(path.join(peopleDir, "test-person.md"), SAMPLE_PERSON, "utf-8");
await fs.writeFile(path.join(peopleDir, "private-person.md"), SAMPLE_PERSON_NO_PAGE, "utf-8");
// First run
await rfc0508Migrator.transform(data, ctx);
const after1a = await fs.readFile(path.join(peopleDir, "test-person.md"), "utf-8");
const after1b = await fs.readFile(path.join(peopleDir, "private-person.md"), "utf-8");
// Second run
await rfc0508Migrator.transform(data, ctx);
const after2a = await fs.readFile(path.join(peopleDir, "test-person.md"), "utf-8");
const after2b = await fs.readFile(path.join(peopleDir, "private-person.md"), "utf-8");
expect(after1a).toBe(after2a);
expect(after1b).toBe(after2b);
} finally {
await fs.rm(tmpDir, { recursive: true, force: true });
}
});
test("rfc-0508 migrator adds participantType: human", async () => {
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "rfc-0508-pbt-"));
const data = makeData(tmpDir);
const ctx = makeCtx();
try {
const peopleDir = path.join(tmpDir, "src", "content", "people", "de");
await fs.mkdir(peopleDir, { recursive: true });
await fs.writeFile(path.join(peopleDir, "test-person.md"), SAMPLE_PERSON, "utf-8");
await rfc0508Migrator.transform(data, ctx);
const content = await fs.readFile(path.join(peopleDir, "test-person.md"), "utf-8");
expect(content).toMatch(/^participantType:\s*human/m);
} finally {
await fs.rm(tmpDir, { recursive: true, force: true });
}
});
test("rfc-0508 migrator adds status and visibility based on page.enabled", async () => {
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "rfc-0508-pbt-"));
const data = makeData(tmpDir);
const ctx = makeCtx();
try {
const peopleDir = path.join(tmpDir, "src", "content", "people", "de");
await fs.mkdir(peopleDir, { recursive: true });
await fs.writeFile(path.join(peopleDir, "test-person.md"), SAMPLE_PERSON, "utf-8");
await fs.writeFile(path.join(peopleDir, "private-person.md"), SAMPLE_PERSON_NO_PAGE, "utf-8");
await rfc0508Migrator.transform(data, ctx);
const publicContent = await fs.readFile(path.join(peopleDir, "test-person.md"), "utf-8");
expect(publicContent).toMatch(/^status:\s*active/m);
expect(publicContent).toMatch(/^visibility:\s*public/m);
const privateContent = await fs.readFile(path.join(peopleDir, "private-person.md"), "utf-8");
expect(privateContent).toMatch(/^status:\s*draft/m);
expect(privateContent).toMatch(/^visibility:\s*private/m);
} finally {
await fs.rm(tmpDir, { recursive: true, force: true });
}
});
test("rfc-0508 migrator derives relationshipType from affiliations", async () => {
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "rfc-0508-pbt-"));
const data = makeData(tmpDir);
const ctx = makeCtx();
try {
const peopleDir = path.join(tmpDir, "src", "content", "people", "de");
await fs.mkdir(peopleDir, { recursive: true });
await fs.writeFile(path.join(peopleDir, "test-person.md"), SAMPLE_PERSON, "utf-8");
await rfc0508Migrator.transform(data, ctx);
const content = await fs.readFile(path.join(peopleDir, "test-person.md"), "utf-8");
expect(content).toMatch(/^relationshipType:\s*founder/m);
} finally {
await fs.rm(tmpDir, { recursive: true, force: true });
}
});
test("rfc-0508 migrator adds consent for public humans with page.enabled", async () => {
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "rfc-0508-pbt-"));
const data = makeData(tmpDir);
const ctx = makeCtx();
try {
const peopleDir = path.join(tmpDir, "src", "content", "people", "de");
await fs.mkdir(peopleDir, { recursive: true });
await fs.writeFile(path.join(peopleDir, "test-person.md"), SAMPLE_PERSON, "utf-8");
await rfc0508Migrator.transform(data, ctx);
const content = await fs.readFile(path.join(peopleDir, "test-person.md"), "utf-8");
expect(content).toMatch(/^consent:/m);
expect(content).toMatch(/consentRecordId:\s*"consent-test-person"/);
expect(content).toMatch(/profileReviewer:\s*"test-person"/);
expect(content).toMatch(/consentDate:\s*"2026-07-24"/);
} finally {
await fs.rm(tmpDir, { recursive: true, force: true });
}
});
test("rfc-0508 migrator does not add consent for private persons", async () => {
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "rfc-0508-pbt-"));
const data = makeData(tmpDir);
const ctx = makeCtx();
try {
const peopleDir = path.join(tmpDir, "src", "content", "people", "de");
await fs.mkdir(peopleDir, { recursive: true });
await fs.writeFile(path.join(peopleDir, "private-person.md"), SAMPLE_PERSON_NO_PAGE, "utf-8");
await rfc0508Migrator.transform(data, ctx);
const content = await fs.readFile(path.join(peopleDir, "private-person.md"), "utf-8");
expect(content).not.toMatch(/^consent:/m);
} finally {
await fs.rm(tmpDir, { recursive: true, force: true });
}
});
test("rfc-0508 migrator handles empty people directory gracefully", async () => {
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "rfc-0508-pbt-"));
const data = makeData(tmpDir);
const ctx = makeCtx();
try {
// No people directory at all
await rfc0508Migrator.transform(data, ctx);
// Should not throw
expect(true).toBe(true);
} finally {
await fs.rm(tmpDir, { recursive: true, force: true });
}
});