mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-30 11:21:07 +00:00
276 lines
8.6 KiB
TypeScript
276 lines
8.6 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { resolveEffectiveToolInventory } from "../../agents/tools-effective-inventory.js";
|
|
import { ErrorCodes } from "../protocol/index.js";
|
|
import { loadSessionEntry } from "../session-utils.js";
|
|
import { toolsEffectiveHandlers } from "./tools-effective.js";
|
|
|
|
vi.mock("../../config/config.js", () => ({
|
|
loadConfig: vi.fn(() => ({})),
|
|
}));
|
|
|
|
vi.mock("../../agents/agent-scope.js", async (importOriginal) => {
|
|
const actual = await importOriginal<typeof import("../../agents/agent-scope.js")>();
|
|
return {
|
|
...actual,
|
|
listAgentIds: vi.fn(() => ["main"]),
|
|
resolveDefaultAgentId: vi.fn(() => "main"),
|
|
resolveSessionAgentId: vi.fn(() => "main"),
|
|
};
|
|
});
|
|
|
|
vi.mock("../../agents/tools-effective-inventory.js", () => ({
|
|
resolveEffectiveToolInventory: vi.fn(() => ({
|
|
agentId: "main",
|
|
profile: "coding",
|
|
groups: [
|
|
{
|
|
id: "core",
|
|
label: "Built-in tools",
|
|
source: "core",
|
|
tools: [
|
|
{
|
|
id: "exec",
|
|
label: "Exec",
|
|
description: "Run shell commands",
|
|
rawDescription: "Run shell commands",
|
|
source: "core",
|
|
},
|
|
],
|
|
},
|
|
],
|
|
})),
|
|
}));
|
|
|
|
vi.mock("../session-utils.js", async (importOriginal) => {
|
|
const actual = await importOriginal<typeof import("../session-utils.js")>();
|
|
return {
|
|
...actual,
|
|
loadSessionEntry: vi.fn(() => ({
|
|
cfg: {},
|
|
canonicalKey: "main:abc",
|
|
entry: {
|
|
sessionId: "session-1",
|
|
updatedAt: 1,
|
|
lastChannel: "telegram",
|
|
lastAccountId: "acct-1",
|
|
lastThreadId: "thread-2",
|
|
lastTo: "channel-1",
|
|
groupId: "group-4",
|
|
groupChannel: "#ops",
|
|
space: "workspace-5",
|
|
chatType: "group",
|
|
modelProvider: "openai",
|
|
model: "gpt-4.1",
|
|
},
|
|
})),
|
|
resolveSessionModelRef: vi.fn(() => ({ provider: "openai", model: "gpt-4.1" })),
|
|
};
|
|
});
|
|
|
|
vi.mock("../../utils/delivery-context.js", async (importOriginal) => {
|
|
const actual = await importOriginal<typeof import("../../utils/delivery-context.js")>();
|
|
return {
|
|
...actual,
|
|
deliveryContextFromSession: vi.fn(() => ({
|
|
channel: "telegram",
|
|
to: "channel-1",
|
|
accountId: "acct-1",
|
|
threadId: "thread-2",
|
|
})),
|
|
};
|
|
});
|
|
|
|
vi.mock("../../auto-reply/reply/reply-threading.js", () => ({
|
|
resolveReplyToMode: vi.fn(() => "first"),
|
|
}));
|
|
|
|
type RespondCall = [boolean, unknown?, { code: number; message: string }?];
|
|
|
|
function createInvokeParams(params: Record<string, unknown>) {
|
|
const respond = vi.fn();
|
|
return {
|
|
respond,
|
|
invoke: async () =>
|
|
await toolsEffectiveHandlers["tools.effective"]({
|
|
params,
|
|
respond: respond as never,
|
|
context: {} as never,
|
|
client: null,
|
|
req: { type: "req", id: "req-1", method: "tools.effective" },
|
|
isWebchatConnect: () => false,
|
|
}),
|
|
};
|
|
}
|
|
|
|
describe("tools.effective handler", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it("rejects invalid params", async () => {
|
|
const { respond, invoke } = createInvokeParams({ includePlugins: false });
|
|
await invoke();
|
|
const call = respond.mock.calls[0] as RespondCall | undefined;
|
|
expect(call?.[0]).toBe(false);
|
|
expect(call?.[2]?.code).toBe(ErrorCodes.INVALID_REQUEST);
|
|
expect(call?.[2]?.message).toContain("invalid tools.effective params");
|
|
});
|
|
|
|
it("rejects missing sessionKey", async () => {
|
|
const { respond, invoke } = createInvokeParams({});
|
|
await invoke();
|
|
const call = respond.mock.calls[0] as RespondCall | undefined;
|
|
expect(call?.[0]).toBe(false);
|
|
expect(call?.[2]?.code).toBe(ErrorCodes.INVALID_REQUEST);
|
|
expect(call?.[2]?.message).toContain("invalid tools.effective params");
|
|
});
|
|
|
|
it("rejects caller-supplied auth context params", async () => {
|
|
const { respond, invoke } = createInvokeParams({ senderIsOwner: true });
|
|
await invoke();
|
|
const call = respond.mock.calls[0] as RespondCall | undefined;
|
|
expect(call?.[0]).toBe(false);
|
|
expect(call?.[2]?.code).toBe(ErrorCodes.INVALID_REQUEST);
|
|
expect(call?.[2]?.message).toContain("invalid tools.effective params");
|
|
});
|
|
|
|
it("rejects unknown agent ids", async () => {
|
|
const { respond, invoke } = createInvokeParams({
|
|
sessionKey: "main:abc",
|
|
agentId: "unknown-agent",
|
|
});
|
|
await invoke();
|
|
const call = respond.mock.calls[0] as RespondCall | undefined;
|
|
expect(call?.[0]).toBe(false);
|
|
expect(call?.[2]?.code).toBe(ErrorCodes.INVALID_REQUEST);
|
|
expect(call?.[2]?.message).toContain("unknown agent id");
|
|
});
|
|
|
|
it("rejects unknown session keys", async () => {
|
|
vi.mocked(loadSessionEntry).mockReturnValueOnce({
|
|
cfg: {},
|
|
canonicalKey: "missing-session",
|
|
entry: undefined,
|
|
legacyKey: undefined,
|
|
storePath: "/tmp/sessions.json",
|
|
} as never);
|
|
const { respond, invoke } = createInvokeParams({ sessionKey: "missing-session" });
|
|
await invoke();
|
|
const call = respond.mock.calls[0] as RespondCall | undefined;
|
|
expect(call?.[0]).toBe(false);
|
|
expect(call?.[2]?.code).toBe(ErrorCodes.INVALID_REQUEST);
|
|
expect(call?.[2]?.message).toContain('unknown session key "missing-session"');
|
|
});
|
|
|
|
it("returns the effective runtime inventory", async () => {
|
|
const { respond, invoke } = createInvokeParams({ sessionKey: "main:abc" });
|
|
await invoke();
|
|
const call = respond.mock.calls[0] as RespondCall | undefined;
|
|
expect(call?.[0]).toBe(true);
|
|
expect(call?.[1]).toMatchObject({
|
|
agentId: "main",
|
|
profile: "coding",
|
|
groups: [
|
|
{
|
|
id: "core",
|
|
source: "core",
|
|
tools: [{ id: "exec", source: "core" }],
|
|
},
|
|
],
|
|
});
|
|
expect(vi.mocked(resolveEffectiveToolInventory)).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
senderIsOwner: false,
|
|
currentChannelId: "channel-1",
|
|
currentThreadTs: "thread-2",
|
|
accountId: "acct-1",
|
|
groupId: "group-4",
|
|
groupChannel: "#ops",
|
|
groupSpace: "workspace-5",
|
|
replyToMode: "first",
|
|
messageProvider: "telegram",
|
|
modelProvider: "openai",
|
|
modelId: "gpt-4.1",
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("falls back to origin.threadId when delivery context omits thread metadata", async () => {
|
|
vi.mocked(loadSessionEntry).mockReturnValueOnce({
|
|
cfg: {},
|
|
canonicalKey: "main:abc",
|
|
entry: {
|
|
sessionId: "session-origin-thread",
|
|
updatedAt: 1,
|
|
lastChannel: "telegram",
|
|
lastAccountId: "acct-1",
|
|
lastTo: "channel-1",
|
|
origin: {
|
|
provider: "telegram",
|
|
accountId: "acct-1",
|
|
threadId: 42,
|
|
},
|
|
groupId: "group-4",
|
|
groupChannel: "#ops",
|
|
space: "workspace-5",
|
|
chatType: "group",
|
|
modelProvider: "openai",
|
|
model: "gpt-4.1",
|
|
},
|
|
} as never);
|
|
const deliveryContextModule = await import("../../utils/delivery-context.js");
|
|
vi.mocked(deliveryContextModule.deliveryContextFromSession).mockReturnValueOnce({
|
|
channel: "telegram",
|
|
to: "channel-1",
|
|
accountId: "acct-1",
|
|
});
|
|
|
|
const { respond, invoke } = createInvokeParams({ sessionKey: "main:abc" });
|
|
await invoke();
|
|
|
|
expect(vi.mocked(resolveEffectiveToolInventory)).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
currentThreadTs: "42",
|
|
}),
|
|
);
|
|
expect((respond.mock.calls[0] as RespondCall | undefined)?.[0]).toBe(true);
|
|
});
|
|
|
|
it("passes senderIsOwner=true for admin-scoped callers", async () => {
|
|
const respond = vi.fn();
|
|
await toolsEffectiveHandlers["tools.effective"]({
|
|
params: { sessionKey: "main:abc" },
|
|
respond: respond as never,
|
|
context: {} as never,
|
|
client: {
|
|
connect: { scopes: ["operator.admin"] },
|
|
} as never,
|
|
req: { type: "req", id: "req-1", method: "tools.effective" },
|
|
isWebchatConnect: () => false,
|
|
});
|
|
expect(vi.mocked(resolveEffectiveToolInventory)).toHaveBeenCalledWith(
|
|
expect.objectContaining({ senderIsOwner: true }),
|
|
);
|
|
});
|
|
|
|
it("rejects agent ids that do not match the session agent", async () => {
|
|
const { respond, invoke } = createInvokeParams({
|
|
sessionKey: "main:abc",
|
|
agentId: "other",
|
|
});
|
|
vi.mocked(loadSessionEntry).mockReturnValueOnce({
|
|
cfg: {},
|
|
canonicalKey: "main:abc",
|
|
entry: {
|
|
sessionId: "session-1",
|
|
updatedAt: 1,
|
|
},
|
|
} as never);
|
|
await invoke();
|
|
const call = respond.mock.calls[0] as RespondCall | undefined;
|
|
expect(call?.[0]).toBe(false);
|
|
expect(call?.[2]?.code).toBe(ErrorCodes.INVALID_REQUEST);
|
|
expect(call?.[2]?.message).toContain('unknown agent id "other"');
|
|
});
|
|
});
|