Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

extensions/the-system/__tests__/board-server.test.ts

Raw
import 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");