mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-26 12:26:38 -06:00
134 lines
3.9 KiB
TypeScript
134 lines
3.9 KiB
TypeScript
// File Transfer tests cover dir list tool plugin behavior.
|
|
import {
|
|
callGatewayTool,
|
|
listNodes,
|
|
resolveNodeIdFromList,
|
|
} from "openclaw/plugin-sdk/agent-harness-runtime";
|
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
import { createDirListTool } from "./dir-list-tool.js";
|
|
|
|
vi.mock("openclaw/plugin-sdk/agent-harness-runtime", () => ({
|
|
callGatewayTool: vi.fn(),
|
|
listNodes: vi.fn(),
|
|
resolveNodeIdFromList: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("../shared/audit.js", () => ({
|
|
appendFileTransferAudit: vi.fn(),
|
|
}));
|
|
|
|
afterEach(() => {
|
|
vi.mocked(callGatewayTool).mockReset();
|
|
vi.mocked(listNodes).mockReset();
|
|
vi.mocked(resolveNodeIdFromList).mockReset();
|
|
});
|
|
|
|
describe("dir_list tool", () => {
|
|
it("exposes the next page token to the model and forwards the current page token", async () => {
|
|
const entries = [
|
|
{ name: "report.txt", isDir: false },
|
|
{ name: "nested", isDir: true },
|
|
];
|
|
vi.mocked(listNodes).mockResolvedValue([{ nodeId: "node-1", displayName: "Node One" }]);
|
|
vi.mocked(resolveNodeIdFromList).mockReturnValue("node-1");
|
|
vi.mocked(callGatewayTool).mockResolvedValue({
|
|
payload: {
|
|
ok: true,
|
|
path: "/tmp/project",
|
|
entries,
|
|
nextPageToken: "3",
|
|
truncated: true,
|
|
},
|
|
});
|
|
|
|
const result = await createDirListTool().execute("tool-call-1", {
|
|
node: "node-1",
|
|
path: "/tmp/project",
|
|
pageToken: "+01",
|
|
maxEntries: 2,
|
|
});
|
|
|
|
expect(result.content).toEqual([
|
|
{
|
|
type: "text",
|
|
text: 'Listed /tmp/project: 1 file, 1 subdir (more entries available). Call dir_list again with pageToken="3".',
|
|
},
|
|
]);
|
|
expect(result.details).toEqual({
|
|
path: "/tmp/project",
|
|
entries,
|
|
nextPageToken: "3",
|
|
truncated: true,
|
|
});
|
|
expect(callGatewayTool).toHaveBeenCalledWith(
|
|
"node.invoke",
|
|
expect.anything(),
|
|
expect.objectContaining({
|
|
nodeId: "node-1",
|
|
command: "dir.list",
|
|
params: {
|
|
path: "/tmp/project",
|
|
pageToken: "+01",
|
|
maxEntries: 2,
|
|
},
|
|
}),
|
|
);
|
|
});
|
|
|
|
it.each([undefined, ""])(
|
|
"reports truncation without inventing an unavailable page token (%s)",
|
|
async (nextPageToken) => {
|
|
vi.mocked(listNodes).mockResolvedValue([{ nodeId: "node-1", displayName: "Node One" }]);
|
|
vi.mocked(resolveNodeIdFromList).mockReturnValue("node-1");
|
|
vi.mocked(callGatewayTool).mockResolvedValue({
|
|
payload: {
|
|
ok: true,
|
|
path: "/tmp/project",
|
|
entries: [],
|
|
nextPageToken,
|
|
truncated: true,
|
|
},
|
|
});
|
|
|
|
const result = await createDirListTool().execute("tool-call-1", {
|
|
node: "node-1",
|
|
path: "/tmp/project",
|
|
});
|
|
|
|
expect(result.content).toEqual([
|
|
{ type: "text", text: "Listed /tmp/project: 0 files, 0 subdirs (more entries available)" },
|
|
]);
|
|
expect(result.details).toEqual({
|
|
path: "/tmp/project",
|
|
entries: [],
|
|
nextPageToken,
|
|
truncated: true,
|
|
});
|
|
},
|
|
);
|
|
|
|
it("reports missing paired nodes before retrying guessed local node names", async () => {
|
|
vi.mocked(listNodes).mockResolvedValue([]);
|
|
|
|
await expect(
|
|
createDirListTool().execute("tool-call-1", {
|
|
node: "local",
|
|
path: "/tmp/project",
|
|
}),
|
|
).rejects.toThrow(
|
|
"no paired nodes available; file-transfer tools require a paired node from nodes status. Use local file/exec tools for local workspace paths.",
|
|
);
|
|
|
|
expect(resolveNodeIdFromList).not.toHaveBeenCalled();
|
|
expect(callGatewayTool).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("describes node as a paired-node reference, not a local alias", () => {
|
|
const schema = JSON.stringify(createDirListTool().parameters);
|
|
|
|
expect(schema).toContain("Existing paired node id");
|
|
expect(schema).toContain("nodes status");
|
|
expect(schema).toContain("local, host, gateway, or auto");
|
|
});
|
|
});
|