repositories / pi-ext
pi-ext
bugabingas pi extensions
owned by admin
extensions/the-system/__tests__/board-server.test.ts
Rawimport assert from "node:assert/strict";
import {
mkdirSync,
mkdtempSync,
readFileSync,
rmSync,
writeFileSync,
} from "node:fs";
import { tmpdir } from "node:os";
import path from "node:path";
import { afterEach, describe, it } from "vitest";
import {
type BoardRenderer,
type BoardServer,
boardSignature,
startBoardServer,
} from "../board-server.ts";
import { planStatusSource } from "../plan-status.ts";
const roots: string[] = [];
const servers: BoardServer[] = [];
afterEach(() => {
for (const server of servers.splice(0)) server.close();
for (const root of roots.splice(0))
rmSync(root, { recursive: true, force: true });
});
describe("board signature", () => {
it("separates document changes from board state changes", () => {
const root = fixture();
const initial = boardSignature(root);
write(root, ".system/board.json", boardJson({}));
const afterBoard = boardSignature(root);
assert.equal(afterBoard.documents, initial.documents);
assert.notEqual(afterBoard.board, initial.board);
write(
root,
".system/specs/PX-SPEC-SPEC0001-contract/index.md",
`${spec()}\nmore\n`,
);
const afterDocument = boardSignature(root);
assert.notEqual(afterDocument.documents, afterBoard.documents);
assert.equal(afterDocument.board, afterBoard.board);
});
});
describe("plan status source", () => {
it("rewrites exactly one status line and preserves the body", () => {
const updated = planStatusSource(plan("draft"), "approved");
assert.equal(updated, plan("approved"));
});
it("preserves CRLF frontmatter", () => {
const source = plan("draft").replaceAll("\n", "\r\n");
assert.equal(
planStatusSource(source, "approved"),
plan("approved").replaceAll("\n", "\r\n"),
);
});
it("rejects documents without a single status line", () => {
assert.throws(
() => planStatusSource("# Plan\n", "approved"),
/frontmatter/,
);
assert.throws(
() =>
planStatusSource(
plan("draft").replace(
"status: draft",
"status: draft\nstatus: draft",
),
"approved",
),
/exactly one status line/,
);
});
});
describe("board server", () => {
it("re-renders stale pages and serves live board state", async () => {
const root = fixture();
let renders = 0;
const client = await open(root, (_index, board, _proposals, revision) => {
renders += 1;
const directory = mkdtempSync(path.join(tmpdir(), "board-site-"));
roots.push(directory);
writeFileSync(
path.join(directory, "index.html"),
`<body data-revision="${revision}" data-renders="${renders}" data-items="${
Object.keys(board.items).length
}">`,
"utf8",
);
return { directory };
});
assert.equal(renders, 1);
assert.match(await client.text("/"), /data-renders="1"/);
assert.match(await client.text("/"), /data-renders="1"/);
const before = await client.json("/api/state");
assert.deepEqual(before.items, {});
assert.equal(typeof before.revision, "string");
const applied = await client.post("/api/state", {
id: "PX-PLAN-PLAN0001",
state: "done",
});
assert.equal(applied.items["PX-PLAN-PLAN0001"].state, "done");
const after = await client.json("/api/state");
assert.equal(after.items["PX-PLAN-PLAN0001"].state, "done");
assert.equal(after.revision, before.revision);
const reloaded = await client.text("/");
assert.match(reloaded, /data-renders="2"/);
assert.match(reloaded, /data-items="1"/);
write(
root,
".system/issues/PX-ISSUE-ISSUE002-later/index.md",
"---\nid: PX-ISSUE-ISSUE002\ntype: issue\ntitle: Later\nspecs: [PX-SPEC-SPEC0001]\n---\n\n# Later\n",
);
const changed = await client.json("/api/state");
assert.notEqual(changed.revision, before.revision);
assert.match(await client.text("/"), /data-renders="3"/);
const late = await client.post("/api/state", {
id: "PX-ISSUE-ISSUE002",
state: "done",
});
assert.equal(late.items["PX-ISSUE-ISSUE002"].state, "done");
});
it("toggles plan status on disk and rejects other documents", async () => {
const root = fixture();
const client = await open(root);
const planPath = ".system/plans/PX-PLAN-PLAN0001-build/index.md";
const result = await client.post("/api/status", {
id: "PX-PLAN-PLAN0001",
status: "approved",
});
assert.deepEqual(result, { id: "PX-PLAN-PLAN0001", status: "approved" });
assert.match(read(root, planPath), /^status: approved$/m);
await client.post("/api/status", {
id: "PX-PLAN-PLAN0001",
status: "draft",
});
assert.equal(read(root, planPath), plan("draft"));
assert.match(
(
await client.failing("/api/status", {
id: "PX-SPEC-SPEC0001",
status: "approved",
})
).error,
/Only plans carry a status/,
);
assert.match(
(
await client.failing("/api/status", {
id: "PX-PLAN-PLAN0001",
status: "shipped",
})
).error,
/Expected plan update with id and status/,
);
});
});
const stubRenderer: BoardRenderer = (_index, _board, _proposals, revision) => {
const directory = mkdtempSync(path.join(tmpdir(), "board-site-"));
roots.push(directory);
writeFileSync(
path.join(directory, "index.html"),
`<body data-revision="${revision}">`,
"utf8",
);
return { directory };
};
async function open(root: string, render: BoardRenderer = stubRenderer) {
const server = await startBoardServer(root, new Map(), render);
servers.push(server);
const url = new URL(server.url);
const handshake = await fetch(server.url, { redirect: "manual" });
const cookie = (handshake.headers.get("set-cookie") ?? "").split(";")[0];
assert.equal(handshake.status, 303);
assert.ok(cookie);
const origin = `${url.protocol}//${url.host}`;
const request = (target: string, init?: RequestInit) =>
fetch(`${origin}${target}`, {
...init,
headers: { ...(init?.headers ?? {}), cookie: cookie ?? "" },
});
const send = (target: string, payload: unknown) =>
request(target, {
method: "POST",
headers: { "content-type": "application/json", origin },
body: JSON.stringify(payload),
});
return {
async text(target: string) {
const response = await request(target);
assert.equal(response.status, 200);
return response.text();
},
async json(target: string) {
const response = await request(target);
assert.equal(response.status, 200);
return (await response.json()) as never;
},
async post(target: string, payload: unknown) {
const response = await send(target, payload);
assert.equal(response.status, 200);
return (await response.json()) as never;
},
async failing(target: string, payload: unknown) {
const response = await send(target, payload);
assert.equal(response.status, 400);
return (await response.json()) as { error: string };
},
};
}
const boardJson = (items: Record<string, unknown>) =>
`${JSON.stringify({ schemaVersion: 0, items }, null, "\t")}\n`;
const spec = () =>
"---\nid: PX-SPEC-SPEC0001\ntype: spec\ntitle: Contract\n---\n\n# Contract\n";
const plan = (status: string) =>
`---\nid: PX-PLAN-PLAN0001\ntype: plan\ntitle: Build\nspec: PX-SPEC-SPEC0001\nstatus: ${status}\n---\n\n# Build\n`;
function fixture(): string {
const root = mkdtempSync(path.join(tmpdir(), "board-server-"));
roots.push(root);
for (const directory of ["specs", "phases", "plans", "issues", "research"])
mkdirSync(path.join(root, ".system", directory), { recursive: true });
write(root, ".system/config.json", '{"prefix":"PX","schemaVersion":0}');
write(root, ".system/board.json", '{"schemaVersion":0,"items":{}}');
write(root, ".system/SYSTEM.md", "# SYSTEM\n");
write(root, ".system/specs/PX-SPEC-SPEC0001-contract/index.md", spec());
write(root, ".system/plans/PX-PLAN-PLAN0001-build/index.md", plan("draft"));
return root;
}
function write(root: string, relative: string, content: string) {
const target = path.join(root, relative);
mkdirSync(path.dirname(target), { recursive: true });
writeFileSync(target, content, "utf8");
}
const read = (root: string, relative: string) =>
readFileSync(path.join(root, relative), "utf8");