forked from EduCraft/curriculum-project-hub
09cadd0148
用 Vitest 组织测试,对齐 Courseware 半边的测试纪律(非散脚本): - test/unit/workspace.test.ts: 路径 confinement(读写回环/..逃逸/绝对路径逃逸/ 父目录创建/列举/不存在 error JSON)—— 11 tests - test/unit/prompt.test.ts: @bot 提取(单 mention/多 mention/纯@bot/无 mention/ 非 JSON/无 text 字段)—— 8 tests - test/unit/permission.test.ts: roleGrantsEdit 单调性(READ 不含 / EDIT 含 / MANAGE 含,ADR-0004 can_mono)—— 3 tests extractPrompt 导出供测试。修了 test helper 默认参数 bug(显式传 undefined 触发 默认值,绕过 mentions 检查)。 vitest run 22/22 通过;tsc rc=0。 Integration 层(trigger 全链路 + lock WellFormed + 真 cph)待本地 postgres 后再起。
81 lines
3.4 KiB
TypeScript
81 lines
3.4 KiB
TypeScript
import { describe, it, expect, beforeEach } from "vitest";
|
|
import { mkdtemp, writeFile, mkdir } from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { ToolRegistry } from "../../src/agent/tools.js";
|
|
import { readFileTool, writeFileTool, listFilesTool, PathEscape } from "../../src/agent/workspace.js";
|
|
|
|
describe("workspace path confinement", () => {
|
|
let ws: string;
|
|
let tools: ToolRegistry;
|
|
|
|
beforeEach(async () => {
|
|
ws = await mkdtemp(join(tmpdir(), "hub-unit-"));
|
|
tools = new ToolRegistry();
|
|
tools.register(readFileTool());
|
|
tools.register(writeFileTool());
|
|
tools.register(listFilesTool());
|
|
await mkdir(join(ws, "sub"));
|
|
await writeFile(join(ws, "a.txt"), "hello");
|
|
await writeFile(join(ws, "sub", "b.txt"), "world");
|
|
});
|
|
|
|
const ctx = () => ({ runId: "r", projectId: "p", boundChatId: "c", workspaceDir: ws });
|
|
|
|
it("reads a file inside the workspace", async () => {
|
|
expect(await tools.execute("read_file", { path: "a.txt" }, ctx())).toBe("hello");
|
|
});
|
|
|
|
it("reads nested files", async () => {
|
|
expect(await tools.execute("read_file", { path: "sub/b.txt" }, ctx())).toBe("world");
|
|
});
|
|
|
|
it("rejects .. escapes as error JSON, not a throw", async () => {
|
|
const out = await tools.execute("read_file", { path: "../../etc/passwd" }, ctx());
|
|
expect((JSON.parse(out) as { error: string }).error).toContain("escapes workspace");
|
|
});
|
|
|
|
it("rejects absolute paths outside the workspace", async () => {
|
|
const out = await tools.execute("read_file", { path: "/etc/passwd" }, ctx());
|
|
expect((JSON.parse(out) as { error: string }).error).toContain("escapes workspace");
|
|
});
|
|
|
|
it("writes a file and reads it back", async () => {
|
|
await tools.execute("write_file", { path: "sub/c.txt", content: "new" }, ctx());
|
|
expect(await tools.execute("read_file", { path: "sub/c.txt" }, ctx())).toBe("new");
|
|
});
|
|
|
|
it("creates parent dirs on write", async () => {
|
|
await tools.execute("write_file", { path: "deep/nested/d.txt", content: "x" }, ctx());
|
|
expect(await tools.execute("read_file", { path: "deep/nested/d.txt" }, ctx())).toBe("x");
|
|
});
|
|
|
|
it("lists the workspace root", async () => {
|
|
const out = await tools.execute("list_files", {}, ctx());
|
|
const entries = JSON.parse(out) as Array<{ name: string; kind: string }>;
|
|
expect(entries).toContainEqual({ name: "a.txt", kind: "file" });
|
|
expect(entries).toContainEqual({ name: "sub", kind: "dir" });
|
|
});
|
|
|
|
it("lists a subdirectory", async () => {
|
|
const out = await tools.execute("list_files", { path: "sub" }, ctx());
|
|
const entries = JSON.parse(out) as Array<{ name: string; kind: string }>;
|
|
expect(entries).toContainEqual({ name: "b.txt", kind: "file" });
|
|
});
|
|
|
|
it("PathEscape is an Error subclass", () => {
|
|
expect(new PathEscape("../x", "/ws")).toBeInstanceOf(Error);
|
|
expect(new PathEscape("../x", "/ws").name).toBe("PathEscape");
|
|
});
|
|
|
|
it("read of nonexistent file returns error JSON", async () => {
|
|
const out = await tools.execute("read_file", { path: "nope.txt" }, ctx());
|
|
expect((JSON.parse(out) as { error: string }).error).toBeTruthy();
|
|
});
|
|
|
|
it("write with .. escape returns error JSON", async () => {
|
|
const out = await tools.execute("write_file", { path: "../escape.txt", content: "x" }, ctx());
|
|
expect((JSON.parse(out) as { error: string }).error).toContain("escapes workspace");
|
|
});
|
|
});
|