import test from "node:test"; import assert from "node:assert/strict"; import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { assertInstanceKey, prepareInstanceWorkspace, recordInstall, removeInstanceWorkspace } from "../src/instance-workspace.js"; function roots() { const base = fs.mkdtempSync(path.join(os.tmpdir(), "dev-server-copy-")); const workspace = path.join(base, "workspace"); const instances = path.join(base, "instances"); fs.mkdirSync(workspace); fs.mkdirSync(instances); return { base, workspace, instances, cleanup: () => fs.rmSync(base, { recursive: true, force: true }) }; } function execution(workspace, key, files) { const directory = path.join(workspace, key); fs.mkdirSync(directory, { recursive: true }); for (const [name, content] of Object.entries(files)) { const file = path.join(directory, name); fs.mkdirSync(path.dirname(file), { recursive: true }); fs.writeFileSync(file, content); } return directory; } test("an execution key must be one safe path segment", () => { for (const bad of ["../escape", "a/b", "", ".", "with space", "x".repeat(65)]) { assert.throws(() => assertInstanceKey(bad), /Invalid execution key/, `accepted ${JSON.stringify(bad)}`); } assert.equal(assertInstanceKey("7f3c1e2a-b4d5"), "7f3c1e2a-b4d5"); }); test("the copy holds the execution's own files and nothing above them", () => { const item = roots(); try { execution(item.workspace, "exec-a", { "package.json": "{}", "src/main.js": "console.log(1)" }); execution(item.workspace, "exec-b", { "secret.txt": "b's work" }); const prepared = prepareInstanceWorkspace({ workspaceRoot: item.workspace, instancesRoot: item.instances, key: "exec-a" }); assert.equal(fs.readFileSync(path.join(prepared.target, "src/main.js"), "utf8"), "console.log(1)"); assert.ok(!fs.existsSync(path.join(prepared.target, "secret.txt"))); assert.equal(prepared.entries, 3); } finally { item.cleanup(); } }); test("a symlink into another execution is dropped, not followed", () => { // The tree is written by the coding agent, so this is the move that would turn the copy - whose // whole purpose is isolation - into a window onto somebody else's work. const item = roots(); try { execution(item.workspace, "exec-b", { "secret.txt": "b's work" }); const source = execution(item.workspace, "exec-a", { "package.json": "{}" }); fs.symlinkSync(path.join(item.workspace, "exec-b"), path.join(source, "peek")); const prepared = prepareInstanceWorkspace({ workspaceRoot: item.workspace, instancesRoot: item.instances, key: "exec-a" }); assert.ok(!fs.existsSync(path.join(prepared.target, "peek"))); assert.ok(!fs.existsSync(path.join(prepared.target, "peek", "secret.txt"))); } finally { item.cleanup(); } }); test("an execution directory replaced by a symlink is refused outright", () => { const item = roots(); try { const outside = path.join(item.base, "outside"); fs.mkdirSync(outside); fs.writeFileSync(path.join(outside, "elsewhere.txt"), "not yours"); fs.symlinkSync(outside, path.join(item.workspace, "exec-a")); assert.throws( () => prepareInstanceWorkspace({ workspaceRoot: item.workspace, instancesRoot: item.instances, key: "exec-a" }), /escapes the workspace root/ ); } finally { item.cleanup(); } }); test("node_modules is not copied, because the install recreates it", () => { const item = roots(); try { execution(item.workspace, "exec-a", { "package.json": "{}", "node_modules/left-pad/index.js": "module.exports=1" }); const prepared = prepareInstanceWorkspace({ workspaceRoot: item.workspace, instancesRoot: item.instances, key: "exec-a" }); assert.ok(!fs.existsSync(path.join(prepared.target, "node_modules"))); } finally { item.cleanup(); } }); test("a refresh replaces project files and keeps the installed dependencies", () => { const item = roots(); try { const source = execution(item.workspace, "exec-a", { "package.json": "{}", "package-lock.json": "{\"v\":1}", "old.js": "gone soon" }); const first = prepareInstanceWorkspace({ workspaceRoot: item.workspace, instancesRoot: item.instances, key: "exec-a" }); fs.mkdirSync(path.join(first.target, "node_modules"), { recursive: true }); fs.writeFileSync(path.join(first.target, "node_modules", "marker"), "installed"); recordInstall(first.target); fs.rmSync(path.join(source, "old.js")); fs.writeFileSync(path.join(source, "new.js"), "fresh"); const second = prepareInstanceWorkspace({ workspaceRoot: item.workspace, instancesRoot: item.instances, key: "exec-a" }); assert.ok(!fs.existsSync(path.join(second.target, "old.js")), "a deleted file must not survive the refresh"); assert.equal(fs.readFileSync(path.join(second.target, "new.js"), "utf8"), "fresh"); assert.equal(fs.readFileSync(path.join(second.target, "node_modules", "marker"), "utf8"), "installed"); assert.equal(second.installIsCurrent, true); } finally { item.cleanup(); } }); test("a changed lockfile makes the recorded install stale", () => { const item = roots(); try { const source = execution(item.workspace, "exec-a", { "package-lock.json": "{\"v\":1}" }); const first = prepareInstanceWorkspace({ workspaceRoot: item.workspace, instancesRoot: item.instances, key: "exec-a" }); fs.mkdirSync(path.join(first.target, "node_modules"), { recursive: true }); recordInstall(first.target); fs.writeFileSync(path.join(source, "package-lock.json"), "{\"v\":2}"); const second = prepareInstanceWorkspace({ workspaceRoot: item.workspace, instancesRoot: item.instances, key: "exec-a" }); assert.equal(second.installIsCurrent, false); } finally { item.cleanup(); } }); test("with no lockfile the install is never called current", () => { const item = roots(); try { execution(item.workspace, "exec-a", { "package.json": "{}" }); const prepared = prepareInstanceWorkspace({ workspaceRoot: item.workspace, instancesRoot: item.instances, key: "exec-a" }); fs.mkdirSync(path.join(prepared.target, "node_modules"), { recursive: true }); recordInstall(prepared.target); const again = prepareInstanceWorkspace({ workspaceRoot: item.workspace, instancesRoot: item.instances, key: "exec-a" }); assert.equal(again.installIsCurrent, false); } finally { item.cleanup(); } }); test("a tree beyond the declared limits is refused instead of filling the volume", () => { const item = roots(); try { execution(item.workspace, "exec-a", { "a.txt": "x".repeat(1024), "b.txt": "y".repeat(1024) }); assert.throws( () => prepareInstanceWorkspace({ workspaceRoot: item.workspace, instancesRoot: item.instances, key: "exec-a", limits: { maxBytes: 1500, maxEntries: 100 } }), /exceeds 1500 bytes/ ); assert.throws( () => prepareInstanceWorkspace({ workspaceRoot: item.workspace, instancesRoot: item.instances, key: "exec-a", limits: { maxBytes: 1e9, maxEntries: 1 } }), /exceeds 1 entries/ ); } finally { item.cleanup(); } }); test("discarding an execution reclaims its directory", () => { const item = roots(); try { execution(item.workspace, "exec-a", { "package.json": "{}" }); const prepared = prepareInstanceWorkspace({ workspaceRoot: item.workspace, instancesRoot: item.instances, key: "exec-a" }); assert.ok(fs.existsSync(prepared.target)); removeInstanceWorkspace(item.instances, "exec-a"); assert.ok(!fs.existsSync(prepared.target)); assert.ok(fs.existsSync(path.join(item.workspace, "exec-a")), "the workspace itself is not ours to delete"); } finally { item.cleanup(); } });