65 lines
3.2 KiB
TypeScript
65 lines
3.2 KiB
TypeScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import fs from "node:fs/promises";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { promisify } from "node:util";
|
|
import { execFile as callback } from "node:child_process";
|
|
import {
|
|
planStructure,
|
|
applyStructure,
|
|
resumeStructure,
|
|
type StructuralRequest,
|
|
} from "../src/capability-language/structural-plan.js";
|
|
const execFile = promisify(callback);
|
|
|
|
test("structural plans validate the graph, journal originals, and reject stale edits", async (context) => {
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), "qx-structural-test-"));
|
|
context.after(() => fs.rm(root, { recursive: true, force: true }));
|
|
await execFile("git", ["-C", root, "init"]);
|
|
await fs.writeFile(path.join(root, ".gitignore"), ".quixos/\n");
|
|
await fs.writeFile(
|
|
path.join(root, "quixos.lock"),
|
|
`quixos-lock version 1 { quixos source { repository "https://example.test/quixos.git"; commit "${"a".repeat(40)}"; } }`,
|
|
);
|
|
const before = `workspace Test id "workspace:test" revision "workspace:test@1" commit "${"b".repeat(40)}" { atom Subject id "atom:subject"; }`;
|
|
await fs.writeFile(path.join(root, "workspace.qx"), before);
|
|
const request: StructuralRequest = {
|
|
kind: "workspace",
|
|
files: [
|
|
{
|
|
file: "workspace.qx",
|
|
edits: [{ operation: "append", parent: { kind: "workspaceDecl" }, source: 'atom Game id "atom:game";' }],
|
|
},
|
|
],
|
|
};
|
|
const plan = await planStructure(root, request);
|
|
assert.equal(await fs.readFile(path.join(root, "workspace.qx"), "utf8"), before);
|
|
await fs.writeFile(path.join(root, "workspace.qx"), `${before}\n// newer edit`);
|
|
await assert.rejects(() => applyStructure(plan), /Stale scaffold/);
|
|
await fs.writeFile(path.join(root, "workspace.qx"), before);
|
|
const applied = await applyStructure(plan);
|
|
assert.equal(applied.phase, "complete");
|
|
assert.ok((await fs.readFile(path.join(root, "workspace.qx"), "utf8")).includes("atom Game"));
|
|
const journal = JSON.parse(await fs.readFile(applied.journalPath, "utf8"));
|
|
assert.equal(journal.changes[0].before, before);
|
|
assert.equal((await resumeStructure(root, applied.id)).phase, "complete");
|
|
// Simulate a process interrupted after one file was written, before recording
|
|
// completion. Replay accepts the already-written exact after image.
|
|
journal.phase = "prepared";
|
|
await fs.writeFile(applied.journalPath, JSON.stringify(journal));
|
|
assert.equal((await resumeStructure(root, applied.id)).phase, "complete");
|
|
await assert.rejects(() => planStructure(root, request), /Duplicate|duplicate/);
|
|
// Cross-repository edits may temporarily refer to an unfinished provider.
|
|
const provisional: StructuralRequest = {
|
|
kind: "workspace",
|
|
validation: "syntax",
|
|
files: [{ file: "workspace.qx", edits: [{ operation: "import", kind: "interface", name: "NotImplementedYet" }] }],
|
|
};
|
|
const draft = await planStructure(root, provisional);
|
|
assert.equal(draft.validation, "syntax");
|
|
await applyStructure(draft);
|
|
assert.match(await fs.readFile(path.join(root, "workspace.qx"), "utf8"), /import interface NotImplementedYet/);
|
|
await assert.rejects(() => planStructure(root, { ...provisional, validation: "resource-graph" }));
|
|
});
|