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 { CommandService } from "../src/command-service.js"; import { buildTaskCommand } from "../src/execution-policy.js"; async function makeWorkspace() { const root = await fs.mkdtemp(path.join(os.tmpdir(), "command-mcp-")); return { root, cleanup: () => fs.rm(root, { recursive: true, force: true }) }; } async function makeNpmProject(root, scripts = {}) { await fs.writeFile(path.join(root, "package.json"), `${JSON.stringify({ name: "fixture", version: "1.0.0", private: true, scripts: { test: "node -e \"process.stdout.write('ok')\"", build: "node -e \"process.stdout.write('build-ok')\"", lint: "node -e \"process.stderr.write('lint-output')\"", slow: "node -e \"setTimeout(() => process.stdout.write('late'), 2000)\"", ...scripts } }, null, 2)}\n`); } test("run_task executes a typed npm build", async () => { const workspace = await makeWorkspace(); try { await makeNpmProject(workspace.root); const result = await new CommandService({ roots: [workspace.root] }).runTask({ runner: "npm", task: "build" }); assert.equal(result.exitCode, 0); assert.match(result.stdout, /build-ok/); } finally { await workspace.cleanup(); } }); test("an empty run_task workdir is normalized to the workspace root", async () => { const workspace = await makeWorkspace(); try { await makeNpmProject(workspace.root); const result = await new CommandService({ roots: [workspace.root] }).runTask({ workdir: "", runner: "npm", task: "test" }); assert.equal(result.exitCode, 0); assert.equal(result.workdir, "."); } finally { await workspace.cleanup(); } }); test("run_task scopes execution to the workspace subpath without adding a session directory", async () => { const workspace = await makeWorkspace(); try { const project = path.join(workspace.root, "project-a"); await fs.mkdir(project, { recursive: true }); await makeNpmProject(project); const result = await new CommandService({ roots: [workspace.root] }).runTask({ runner: "npm", task: "test", sessionId: "sess_1", workspaceSubpath: "project-a" }); assert.equal(result.exitCode, 0); assert.match(result.stdout, /ok/); } finally { await workspace.cleanup(); } }); test("typed runners reject unsupported tasks and paths", () => { assert.throws(() => buildTaskCommand({ runner: "pytest", task: "build" }), /only the test task/); assert.throws(() => buildTaskCommand({ runner: "npm", task: "test", paths: ["tests"] }), /does not accept paths/); assert.throws(() => buildTaskCommand({ runner: "shell", task: "test" }), /runner is not supported/); assert.deepEqual(buildTaskCommand({ runner: "cargo", task: "lint" }), ["cargo", "clippy"]); }); test("run_task rejects traversal, absolute workdirs, options, and escaped task paths", async () => { const workspace = await makeWorkspace(); try { await makeNpmProject(workspace.root); const service = new CommandService({ roots: [workspace.root] }); await assert.rejects(service.runTask({ workdir: "../outside", runner: "npm", task: "test" }), /relative path|escapes/); await assert.rejects(service.runTask({ workdir: workspace.root, runner: "npm", task: "test" }), /relative path/); await assert.rejects(service.runTask({ runner: "pytest", task: "test", paths: ["--rootdir=/tmp"] }), /not options/); await assert.rejects(service.runTask({ runner: "pytest", task: "test", paths: ["../outside"] }), /outside/); } finally { await workspace.cleanup(); } }); test("run_task uses a minimal environment", async () => { const workspace = await makeWorkspace(); const original = process.env.MCP_TEST_SECRET; process.env.MCP_TEST_SECRET = "must-not-reach-child"; try { await makeNpmProject(workspace.root, { test: "node -e \"process.stdout.write(process.env.MCP_TEST_SECRET || 'clean')\"" }); const result = await new CommandService({ roots: [workspace.root] }).runTask({ runner: "npm", task: "test" }); assert.equal(result.stdout.includes("must-not-reach-child"), false); assert.match(result.stdout, /clean/); } finally { if (original === undefined) delete process.env.MCP_TEST_SECRET; else process.env.MCP_TEST_SECRET = original; await workspace.cleanup(); } }); test("run_task enforces timeouts and output limits", async () => { const workspace = await makeWorkspace(); try { await makeNpmProject(workspace.root, { test: "node -e \"process.stdout.write('x'.repeat(5000))\"", build: "node -e \"setTimeout(() => process.stdout.write('late'), 2000)\"" }); const service = new CommandService({ roots: [workspace.root] }); const large = await service.runTask({ runner: "npm", task: "test", outputByteLimit: 1024 }); assert.equal(large.stdoutTruncated, true); const slow = await service.runTask({ runner: "npm", task: "build", timeoutSeconds: 1 }); assert.equal(slow.timedOut, true); } finally { await workspace.cleanup(); } }); test("disabled execution backend fails closed", async () => { const workspace = await makeWorkspace(); try { await makeNpmProject(workspace.root); const service = new CommandService({ roots: [workspace.root], executionBackend: "disabled" }); await assert.rejects(service.runTask({ runner: "npm", task: "test" }), /disabled/); } finally { await workspace.cleanup(); } });