6bdba2d3c9
- Assert the eval tool hides disabled backends from the model-facing wire schema (language enum + field descriptions), summary, and description by default (rb/jl off), and advertises them once enabled — including the enabled-subset case. - Update the env-flag fallback test for the new rb/jl opt-in defaults and extend the env guard to PI_RB/PI_JL so the suite is shell-independent. - Note the opt-in default and dynamic advertising in the changelog.
159 lines
4.9 KiB
TypeScript
159 lines
4.9 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from "bun:test";
|
|
import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings";
|
|
import * as evalIndex from "@oh-my-pi/pi-coding-agent/eval";
|
|
import * as pyKernel from "@oh-my-pi/pi-coding-agent/eval/py/kernel";
|
|
import type { ToolSession } from "@oh-my-pi/pi-coding-agent/tools";
|
|
import { EvalTool } from "@oh-my-pi/pi-coding-agent/tools/eval";
|
|
import { resolveEvalBackends } from "@oh-my-pi/pi-coding-agent/tools/eval-backends";
|
|
|
|
let originalPiPy: string | undefined;
|
|
let originalPiJs: string | undefined;
|
|
let originalPiRb: string | undefined;
|
|
let originalPiJl: string | undefined;
|
|
|
|
function restoreEnv(name: "PI_PY" | "PI_JS" | "PI_RB" | "PI_JL", value: string | undefined): void {
|
|
if (value === undefined) {
|
|
delete Bun.env[name];
|
|
return;
|
|
}
|
|
Bun.env[name] = value;
|
|
}
|
|
function makeSession(settings = Settings.isolated()): ToolSession {
|
|
return {
|
|
cwd: "/tmp/eval-test",
|
|
hasUI: false,
|
|
getSessionFile: () => null,
|
|
getSessionSpawns: () => null,
|
|
settings,
|
|
};
|
|
}
|
|
|
|
const mockResult = {
|
|
output: "ok",
|
|
exitCode: 0,
|
|
cancelled: false,
|
|
truncated: false,
|
|
artifactId: undefined,
|
|
totalLines: 1,
|
|
totalBytes: 2,
|
|
outputLines: 1,
|
|
outputBytes: 2,
|
|
displayOutputs: [],
|
|
};
|
|
|
|
describe("EvalTool language dispatch", () => {
|
|
beforeEach(() => {
|
|
originalPiPy = Bun.env.PI_PY;
|
|
originalPiJs = Bun.env.PI_JS;
|
|
originalPiRb = Bun.env.PI_RB;
|
|
originalPiJl = Bun.env.PI_JL;
|
|
delete Bun.env.PI_PY;
|
|
delete Bun.env.PI_JS;
|
|
delete Bun.env.PI_RB;
|
|
delete Bun.env.PI_JL;
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
restoreEnv("PI_PY", originalPiPy);
|
|
restoreEnv("PI_JS", originalPiJs);
|
|
restoreEnv("PI_RB", originalPiRb);
|
|
restoreEnv("PI_JL", originalPiJl);
|
|
});
|
|
|
|
it('dispatches to the JS backend when cell.language === "js"', async () => {
|
|
const jsExecuteSpy = vi.spyOn(evalIndex.jsBackend, "execute").mockResolvedValue(mockResult);
|
|
const pythonExecuteSpy = vi.spyOn(evalIndex.pythonBackend, "execute");
|
|
|
|
const tool = new EvalTool(makeSession());
|
|
await tool.execute("call-js", {
|
|
cells: [{ language: "js", code: "const x = 1;" }],
|
|
});
|
|
|
|
expect(jsExecuteSpy).toHaveBeenCalledTimes(1);
|
|
expect(pythonExecuteSpy).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('dispatches to the Python backend when cell.language === "py"', async () => {
|
|
vi.spyOn(pyKernel, "checkPythonKernelAvailability").mockResolvedValue({ ok: true });
|
|
vi.spyOn(evalIndex.pythonBackend, "isAvailable").mockResolvedValue(true);
|
|
const pythonExecuteSpy = vi.spyOn(evalIndex.pythonBackend, "execute").mockResolvedValue(mockResult);
|
|
const jsExecuteSpy = vi.spyOn(evalIndex.jsBackend, "execute");
|
|
|
|
const tool = new EvalTool(makeSession());
|
|
await tool.execute("call-py", {
|
|
cells: [{ language: "py", code: "print('hi')" }],
|
|
});
|
|
|
|
expect(pythonExecuteSpy).toHaveBeenCalledTimes(1);
|
|
expect(jsExecuteSpy).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("interleaves backends across cells in a single call", async () => {
|
|
vi.spyOn(pyKernel, "checkPythonKernelAvailability").mockResolvedValue({ ok: true });
|
|
vi.spyOn(evalIndex.pythonBackend, "isAvailable").mockResolvedValue(true);
|
|
const pythonExecuteSpy = vi.spyOn(evalIndex.pythonBackend, "execute").mockResolvedValue(mockResult);
|
|
const jsExecuteSpy = vi.spyOn(evalIndex.jsBackend, "execute").mockResolvedValue(mockResult);
|
|
|
|
const tool = new EvalTool(makeSession());
|
|
await tool.execute("call-mixed", {
|
|
cells: [
|
|
{ language: "py", code: "x = 1" },
|
|
{ language: "js", code: "const y = 2;" },
|
|
],
|
|
});
|
|
|
|
expect(pythonExecuteSpy).toHaveBeenCalledTimes(1);
|
|
expect(jsExecuteSpy).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("rejects py cells when eval.py is disabled", async () => {
|
|
const settings = Settings.isolated();
|
|
settings.set("eval.py", false);
|
|
const tool = new EvalTool(makeSession(settings));
|
|
await expect(
|
|
tool.execute("call-py-disabled", {
|
|
cells: [{ language: "py", code: "print('hi')" }],
|
|
}),
|
|
).rejects.toThrow(/eval\.py = false/);
|
|
});
|
|
|
|
it("rejects js cells when eval.js is disabled", async () => {
|
|
const settings = Settings.isolated();
|
|
settings.set("eval.js", false);
|
|
const tool = new EvalTool(makeSession(settings));
|
|
await expect(
|
|
tool.execute("call-js-disabled", {
|
|
cells: [{ language: "js", code: "const x = 1;" }],
|
|
}),
|
|
).rejects.toThrow(/eval\.js = false/);
|
|
});
|
|
|
|
it("uses settings for eval backends whose env flag is unset", () => {
|
|
Bun.env.PI_PY = "1";
|
|
const settings = Settings.isolated();
|
|
settings.set("eval.py", false);
|
|
settings.set("eval.js", false);
|
|
|
|
expect(resolveEvalBackends(makeSession(settings))).toEqual({
|
|
python: true,
|
|
js: false,
|
|
ruby: false,
|
|
julia: false,
|
|
});
|
|
});
|
|
|
|
it("lets PI_JS disable js execution even when eval.js is enabled", async () => {
|
|
Bun.env.PI_JS = "0";
|
|
const settings = Settings.isolated();
|
|
settings.set("eval.js", true);
|
|
const tool = new EvalTool(makeSession(settings));
|
|
|
|
await expect(
|
|
tool.execute("call-js-env-disabled", {
|
|
cells: [{ language: "js", code: "const x = 1;" }],
|
|
}),
|
|
).rejects.toThrow(/PI_JS=0/);
|
|
});
|
|
});
|