mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-19 01:04:45 +00:00
test: clear gateway tools invoke broad matchers
This commit is contained in:
@@ -393,6 +393,12 @@ const expectOkInvokeResponse = async (res: Response) => {
|
||||
return body as { ok: boolean; result?: Record<string, unknown> };
|
||||
};
|
||||
|
||||
const firstHookCallArg = () => {
|
||||
const call = hookMocks.runBeforeToolCallHook.mock.calls[0];
|
||||
expect(call).toBeDefined();
|
||||
return call?.[0] as RunBeforeToolCallHookArgs;
|
||||
};
|
||||
|
||||
const invokeToolsRpc = async (params: Record<string, unknown>, scopes = ["operator.write"]) => {
|
||||
const respond = vi.fn();
|
||||
await toolsInvokeHandlers["tools.invoke"]({
|
||||
@@ -442,17 +448,14 @@ describe("POST /tools/invoke", () => {
|
||||
expect(body).toHaveProperty("result");
|
||||
expect(lastCreateOpenClawToolsContext?.allowMediaInvokeCommands).toBe(true);
|
||||
expect(lastCreateOpenClawToolsContext?.disablePluginTools).toBe(true);
|
||||
expect(hookMocks.runBeforeToolCallHook).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
toolName: "agents_list",
|
||||
ctx: expect.objectContaining({
|
||||
agentId: "main",
|
||||
config: cfg,
|
||||
sessionKey: "agent:main:main",
|
||||
loopDetection: { warnAt: 3 },
|
||||
}),
|
||||
}),
|
||||
);
|
||||
const hookArg = firstHookCallArg();
|
||||
expect(hookArg.toolName).toBe("agents_list");
|
||||
const hookCtx = hookArg.ctx;
|
||||
expect(hookCtx).toBeDefined();
|
||||
expect(hookCtx?.agentId).toBe("main");
|
||||
expect(hookCtx?.config).toBe(cfg);
|
||||
expect(hookCtx?.sessionKey).toBe("agent:main:main");
|
||||
expect(hookCtx?.loopDetection).toEqual({ warnAt: 3 });
|
||||
});
|
||||
|
||||
it("opts direct gateway tool invocation into gateway subagent binding", async () => {
|
||||
@@ -489,10 +492,9 @@ describe("POST /tools/invoke", () => {
|
||||
});
|
||||
|
||||
const body = await expectOkInvokeResponse(res);
|
||||
expect(body.result).toMatchObject({ ok: true, permissionFlow: true });
|
||||
expect(lastCreateOpenClawToolsContext?.pluginToolAllowlist).toEqual(
|
||||
expect.arrayContaining(["plugin_doctor"]),
|
||||
);
|
||||
expect(body.result?.ok).toBe(true);
|
||||
expect(body.result?.permissionFlow).toBe(true);
|
||||
expect(lastCreateOpenClawToolsContext?.pluginToolAllowlist).toContain("plugin_doctor");
|
||||
});
|
||||
|
||||
it("uses tools.alsoAllow for optional plugin discovery without loading every plugin tool", async () => {
|
||||
@@ -508,10 +510,9 @@ describe("POST /tools/invoke", () => {
|
||||
});
|
||||
|
||||
const body = await expectOkInvokeResponse(res);
|
||||
expect(body.result).toMatchObject({ ok: true, permissionFlow: true });
|
||||
expect(lastCreateOpenClawToolsContext?.pluginToolAllowlist).toEqual(
|
||||
expect.arrayContaining(["plugin_doctor"]),
|
||||
);
|
||||
expect(body.result?.ok).toBe(true);
|
||||
expect(body.result?.permissionFlow).toBe(true);
|
||||
expect(lastCreateOpenClawToolsContext?.pluginToolAllowlist).toContain("plugin_doctor");
|
||||
expect(lastCreateOpenClawToolsContext?.pluginToolAllowlist).not.toContain("*");
|
||||
});
|
||||
|
||||
@@ -529,13 +530,10 @@ describe("POST /tools/invoke", () => {
|
||||
});
|
||||
|
||||
expect(res.status).toBe(403);
|
||||
await expect(res.json()).resolves.toMatchObject({
|
||||
ok: false,
|
||||
error: {
|
||||
type: "tool_call_blocked",
|
||||
message: "blocked by test hook",
|
||||
},
|
||||
});
|
||||
const body = await res.json();
|
||||
expect(body.ok).toBe(false);
|
||||
expect(body.error?.type).toBe("tool_call_blocked");
|
||||
expect(body.error?.message).toBe("blocked by test hook");
|
||||
});
|
||||
|
||||
it("accepts shared-secret bearer auth on the HTTP tools surface", async () => {
|
||||
@@ -587,7 +585,7 @@ describe("POST /tools/invoke", () => {
|
||||
});
|
||||
|
||||
const body = await expectOkInvokeResponse(res);
|
||||
expect(body.result).toMatchObject({ ok: true });
|
||||
expect(body.result?.ok).toBe(true);
|
||||
});
|
||||
|
||||
it("supports tools.alsoAllow in profile and implicit modes", async () => {
|
||||
@@ -866,13 +864,10 @@ describe("POST /tools/invoke", () => {
|
||||
});
|
||||
|
||||
expect(res.status).toBe(403);
|
||||
await expect(res.json()).resolves.toMatchObject({
|
||||
ok: false,
|
||||
error: {
|
||||
type: "forbidden",
|
||||
message: "missing scope: operator.write",
|
||||
},
|
||||
});
|
||||
const body = await res.json();
|
||||
expect(body.ok).toBe(false);
|
||||
expect(body.error?.type).toBe("forbidden");
|
||||
expect(body.error?.message).toBe("missing scope: operator.write");
|
||||
});
|
||||
|
||||
it("treats shared-secret bearer auth as full operator access on /tools/invoke", async () => {
|
||||
@@ -982,25 +977,20 @@ describe("tools.invoke Gateway RPC", () => {
|
||||
});
|
||||
|
||||
expect(call?.[0]).toBe(true);
|
||||
expect(call?.[1]).toMatchObject({
|
||||
ok: true,
|
||||
toolName: "agents_list",
|
||||
output: { ok: true, result: [] },
|
||||
source: "core",
|
||||
});
|
||||
expect(call?.[1]?.ok).toBe(true);
|
||||
expect(call?.[1]?.toolName).toBe("agents_list");
|
||||
expect(call?.[1]?.output).toEqual({ ok: true, result: [] });
|
||||
expect((call?.[1] as { source?: unknown } | undefined)?.source).toBe("core");
|
||||
expect(lastCreateOpenClawToolsContext?.allowGatewaySubagentBinding).toBe(true);
|
||||
expect(hookMocks.runBeforeToolCallHook).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
approvalMode: "report",
|
||||
toolName: "agents_list",
|
||||
toolCallId: "rpc-rpc-tool-test",
|
||||
ctx: expect.objectContaining({
|
||||
agentId: "main",
|
||||
config: cfg,
|
||||
sessionKey: "agent:main:main",
|
||||
}),
|
||||
}),
|
||||
);
|
||||
const hookArg = firstHookCallArg();
|
||||
expect(hookArg.approvalMode).toBe("report");
|
||||
expect(hookArg.toolName).toBe("agents_list");
|
||||
expect(hookArg.toolCallId).toBe("rpc-rpc-tool-test");
|
||||
const hookCtx = hookArg.ctx;
|
||||
expect(hookCtx).toBeDefined();
|
||||
expect(hookCtx?.agentId).toBe("main");
|
||||
expect(hookCtx?.config).toBe(cfg);
|
||||
expect(hookCtx?.sessionKey).toBe("agent:main:main");
|
||||
});
|
||||
|
||||
it("returns typed approval-needed refusal when the policy hook blocks", async () => {
|
||||
@@ -1020,15 +1010,12 @@ describe("tools.invoke Gateway RPC", () => {
|
||||
});
|
||||
|
||||
expect(call?.[0]).toBe(true);
|
||||
expect(call?.[1]).toMatchObject({
|
||||
ok: false,
|
||||
toolName: "tools_invoke_test",
|
||||
requiresApproval: true,
|
||||
error: {
|
||||
code: "requires_approval",
|
||||
message: "Plugin approval required",
|
||||
},
|
||||
});
|
||||
expect(call?.[1]?.ok).toBe(false);
|
||||
expect(call?.[1]?.toolName).toBe("tools_invoke_test");
|
||||
expect((call?.[1] as { requiresApproval?: unknown } | undefined)?.requiresApproval).toBe(true);
|
||||
const error = call?.[1]?.error as { code?: string; message?: string } | undefined;
|
||||
expect(error?.code).toBe("requires_approval");
|
||||
expect(error?.message).toBe("Plugin approval required");
|
||||
});
|
||||
|
||||
it("rejects mismatched session and agent scope", async () => {
|
||||
@@ -1048,23 +1035,19 @@ describe("tools.invoke Gateway RPC", () => {
|
||||
});
|
||||
|
||||
expect(call?.[0]).toBe(true);
|
||||
expect(call?.[1]).toMatchObject({
|
||||
ok: false,
|
||||
toolName: "agents_list",
|
||||
error: {
|
||||
code: "validation_error",
|
||||
message: 'agent id "other" does not match session agent "main"',
|
||||
},
|
||||
});
|
||||
expect(call?.[1]?.ok).toBe(false);
|
||||
expect(call?.[1]?.toolName).toBe("agents_list");
|
||||
const error = call?.[1]?.error as { code?: string; message?: string } | undefined;
|
||||
expect(error?.code).toBe("validation_error");
|
||||
expect(error?.message).toBe('agent id "other" does not match session agent "main"');
|
||||
});
|
||||
|
||||
it("rejects malformed params at the RPC boundary", async () => {
|
||||
const call = await invokeToolsRpc({ name: "" });
|
||||
|
||||
expect(call?.[0]).toBe(false);
|
||||
expect(call?.[2]).toMatchObject({
|
||||
code: "INVALID_REQUEST",
|
||||
message: expect.stringContaining("invalid tools.invoke params"),
|
||||
});
|
||||
const error = call?.[2] as { code?: string; message?: string } | undefined;
|
||||
expect(error?.code).toBe("INVALID_REQUEST");
|
||||
expect(error?.message).toContain("invalid tools.invoke params");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user