mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 19:04:31 +00:00
perf(test): consolidate sessions tool e2e suites
This commit is contained in:
@@ -1,103 +0,0 @@
|
|||||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
||||||
import { createTestRegistry } from "../../test-utils/channel-plugins.js";
|
|
||||||
|
|
||||||
const callGatewayMock = vi.fn();
|
|
||||||
vi.mock("../../gateway/call.js", () => ({
|
|
||||||
callGateway: (opts: unknown) => callGatewayMock(opts),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const loadResolveAnnounceTarget = async () => await import("./sessions-announce-target.js");
|
|
||||||
|
|
||||||
const installRegistry = async () => {
|
|
||||||
const { setActivePluginRegistry } = await import("../../plugins/runtime.js");
|
|
||||||
setActivePluginRegistry(
|
|
||||||
createTestRegistry([
|
|
||||||
{
|
|
||||||
pluginId: "discord",
|
|
||||||
source: "test",
|
|
||||||
plugin: {
|
|
||||||
id: "discord",
|
|
||||||
meta: {
|
|
||||||
id: "discord",
|
|
||||||
label: "Discord",
|
|
||||||
selectionLabel: "Discord",
|
|
||||||
docsPath: "/channels/discord",
|
|
||||||
blurb: "Discord test stub.",
|
|
||||||
},
|
|
||||||
capabilities: { chatTypes: ["direct", "channel", "thread"] },
|
|
||||||
config: {
|
|
||||||
listAccountIds: () => ["default"],
|
|
||||||
resolveAccount: () => ({}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pluginId: "whatsapp",
|
|
||||||
source: "test",
|
|
||||||
plugin: {
|
|
||||||
id: "whatsapp",
|
|
||||||
meta: {
|
|
||||||
id: "whatsapp",
|
|
||||||
label: "WhatsApp",
|
|
||||||
selectionLabel: "WhatsApp",
|
|
||||||
docsPath: "/channels/whatsapp",
|
|
||||||
blurb: "WhatsApp test stub.",
|
|
||||||
preferSessionLookupForAnnounceTarget: true,
|
|
||||||
},
|
|
||||||
capabilities: { chatTypes: ["direct", "group"] },
|
|
||||||
config: {
|
|
||||||
listAccountIds: () => ["default"],
|
|
||||||
resolveAccount: () => ({}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
describe("resolveAnnounceTarget", () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
callGatewayMock.mockReset();
|
|
||||||
await installRegistry();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("derives non-WhatsApp announce targets from the session key", async () => {
|
|
||||||
const { resolveAnnounceTarget } = await loadResolveAnnounceTarget();
|
|
||||||
const target = await resolveAnnounceTarget({
|
|
||||||
sessionKey: "agent:main:discord:group:dev",
|
|
||||||
displayKey: "agent:main:discord:group:dev",
|
|
||||||
});
|
|
||||||
expect(target).toEqual({ channel: "discord", to: "channel:dev" });
|
|
||||||
expect(callGatewayMock).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("hydrates WhatsApp accountId from sessions.list when available", async () => {
|
|
||||||
const { resolveAnnounceTarget } = await loadResolveAnnounceTarget();
|
|
||||||
callGatewayMock.mockResolvedValueOnce({
|
|
||||||
sessions: [
|
|
||||||
{
|
|
||||||
key: "agent:main:whatsapp:group:123@g.us",
|
|
||||||
deliveryContext: {
|
|
||||||
channel: "whatsapp",
|
|
||||||
to: "123@g.us",
|
|
||||||
accountId: "work",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
const target = await resolveAnnounceTarget({
|
|
||||||
sessionKey: "agent:main:whatsapp:group:123@g.us",
|
|
||||||
displayKey: "agent:main:whatsapp:group:123@g.us",
|
|
||||||
});
|
|
||||||
expect(target).toEqual({
|
|
||||||
channel: "whatsapp",
|
|
||||||
to: "123@g.us",
|
|
||||||
accountId: "work",
|
|
||||||
});
|
|
||||||
expect(callGatewayMock).toHaveBeenCalledTimes(1);
|
|
||||||
const first = callGatewayMock.mock.calls[0]?.[0] as { method?: string } | undefined;
|
|
||||||
expect(first).toBeDefined();
|
|
||||||
expect(first?.method).toBe("sessions.list");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
|
||||||
import { extractAssistantText, sanitizeTextContent } from "./sessions-helpers.js";
|
|
||||||
|
|
||||||
describe("sanitizeTextContent", () => {
|
|
||||||
it("strips minimax tool call XML and downgraded markers", () => {
|
|
||||||
const input =
|
|
||||||
'Hello <invoke name="tool">payload</invoke></minimax:tool_call> ' +
|
|
||||||
"[Tool Call: foo (ID: 1)] world";
|
|
||||||
const result = sanitizeTextContent(input).trim();
|
|
||||||
expect(result).toBe("Hello world");
|
|
||||||
expect(result).not.toContain("invoke");
|
|
||||||
expect(result).not.toContain("Tool Call");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("strips thinking tags", () => {
|
|
||||||
const input = "Before <think>secret</think> after";
|
|
||||||
const result = sanitizeTextContent(input).trim();
|
|
||||||
expect(result).toBe("Before after");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("extractAssistantText", () => {
|
|
||||||
it("sanitizes blocks without injecting newlines", () => {
|
|
||||||
const message = {
|
|
||||||
role: "assistant",
|
|
||||||
content: [
|
|
||||||
{ type: "text", text: "Hi " },
|
|
||||||
{ type: "text", text: "<think>secret</think>there" },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
expect(extractAssistantText(message)).toBe("Hi there");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("rewrites error-ish assistant text only when the transcript marks it as an error", () => {
|
|
||||||
const message = {
|
|
||||||
role: "assistant",
|
|
||||||
stopReason: "error",
|
|
||||||
errorMessage: "500 Internal Server Error",
|
|
||||||
content: [{ type: "text", text: "500 Internal Server Error" }],
|
|
||||||
};
|
|
||||||
expect(extractAssistantText(message)).toBe("HTTP 500: Internal Server Error");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("keeps normal status text that mentions billing", () => {
|
|
||||||
const message = {
|
|
||||||
role: "assistant",
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
type: "text",
|
|
||||||
text: "Firebase downgraded us to the free Spark plan. Check whether billing should be re-enabled.",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
expect(extractAssistantText(message)).toBe(
|
|
||||||
"Firebase downgraded us to the free Spark plan. Check whether billing should be re-enabled.",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
||||||
|
|
||||||
const callGatewayMock = vi.fn();
|
|
||||||
vi.mock("../../gateway/call.js", () => ({
|
|
||||||
callGateway: (opts: unknown) => callGatewayMock(opts),
|
|
||||||
}));
|
|
||||||
|
|
||||||
vi.mock("../../config/config.js", async (importOriginal) => {
|
|
||||||
const actual = await importOriginal<typeof import("../../config/config.js")>();
|
|
||||||
return {
|
|
||||||
...actual,
|
|
||||||
loadConfig: () =>
|
|
||||||
({
|
|
||||||
session: { scope: "per-sender", mainKey: "main" },
|
|
||||||
tools: { agentToAgent: { enabled: false } },
|
|
||||||
}) as never,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
import { createSessionsListTool } from "./sessions-list-tool.js";
|
|
||||||
|
|
||||||
describe("sessions_list gating", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
callGatewayMock.mockReset();
|
|
||||||
callGatewayMock.mockResolvedValue({
|
|
||||||
path: "/tmp/sessions.json",
|
|
||||||
sessions: [
|
|
||||||
{ key: "agent:main:main", kind: "direct" },
|
|
||||||
{ key: "agent:other:main", kind: "direct" },
|
|
||||||
],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("filters out other agents when tools.agentToAgent.enabled is false", async () => {
|
|
||||||
const tool = createSessionsListTool({ agentSessionKey: "agent:main:main" });
|
|
||||||
const result = await tool.execute("call1", {});
|
|
||||||
expect(result.details).toMatchObject({
|
|
||||||
count: 1,
|
|
||||||
sessions: [{ key: "agent:main:main" }],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
||||||
|
|
||||||
const callGatewayMock = vi.fn();
|
|
||||||
vi.mock("../../gateway/call.js", () => ({
|
|
||||||
callGateway: (opts: unknown) => callGatewayMock(opts),
|
|
||||||
}));
|
|
||||||
|
|
||||||
vi.mock("../../config/config.js", async (importOriginal) => {
|
|
||||||
const actual = await importOriginal<typeof import("../../config/config.js")>();
|
|
||||||
return {
|
|
||||||
...actual,
|
|
||||||
loadConfig: () =>
|
|
||||||
({
|
|
||||||
session: { scope: "per-sender", mainKey: "main" },
|
|
||||||
tools: { agentToAgent: { enabled: false } },
|
|
||||||
}) as never,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
import { createSessionsSendTool } from "./sessions-send-tool.js";
|
|
||||||
|
|
||||||
describe("sessions_send gating", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
callGatewayMock.mockReset();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("blocks cross-agent sends when tools.agentToAgent.enabled is false", async () => {
|
|
||||||
const tool = createSessionsSendTool({
|
|
||||||
agentSessionKey: "agent:main:main",
|
|
||||||
agentChannel: "whatsapp",
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await tool.execute("call1", {
|
|
||||||
sessionKey: "agent:other:main",
|
|
||||||
message: "hi",
|
|
||||||
timeoutSeconds: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(callGatewayMock).not.toHaveBeenCalled();
|
|
||||||
expect(result.details).toMatchObject({ status: "forbidden" });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
219
src/agents/tools/sessions.e2e.test.ts
Normal file
219
src/agents/tools/sessions.e2e.test.ts
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
import { createTestRegistry } from "../../test-utils/channel-plugins.js";
|
||||||
|
import { extractAssistantText, sanitizeTextContent } from "./sessions-helpers.js";
|
||||||
|
|
||||||
|
const callGatewayMock = vi.fn();
|
||||||
|
vi.mock("../../gateway/call.js", () => ({
|
||||||
|
callGateway: (opts: unknown) => callGatewayMock(opts),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("../../config/config.js", async (importOriginal) => {
|
||||||
|
const actual = await importOriginal<typeof import("../../config/config.js")>();
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
loadConfig: () =>
|
||||||
|
({
|
||||||
|
session: { scope: "per-sender", mainKey: "main" },
|
||||||
|
tools: { agentToAgent: { enabled: false } },
|
||||||
|
}) as never,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
import { createSessionsListTool } from "./sessions-list-tool.js";
|
||||||
|
import { createSessionsSendTool } from "./sessions-send-tool.js";
|
||||||
|
|
||||||
|
const loadResolveAnnounceTarget = async () => await import("./sessions-announce-target.js");
|
||||||
|
|
||||||
|
const installRegistry = async () => {
|
||||||
|
const { setActivePluginRegistry } = await import("../../plugins/runtime.js");
|
||||||
|
setActivePluginRegistry(
|
||||||
|
createTestRegistry([
|
||||||
|
{
|
||||||
|
pluginId: "discord",
|
||||||
|
source: "test",
|
||||||
|
plugin: {
|
||||||
|
id: "discord",
|
||||||
|
meta: {
|
||||||
|
id: "discord",
|
||||||
|
label: "Discord",
|
||||||
|
selectionLabel: "Discord",
|
||||||
|
docsPath: "/channels/discord",
|
||||||
|
blurb: "Discord test stub.",
|
||||||
|
},
|
||||||
|
capabilities: { chatTypes: ["direct", "channel", "thread"] },
|
||||||
|
config: {
|
||||||
|
listAccountIds: () => ["default"],
|
||||||
|
resolveAccount: () => ({}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pluginId: "whatsapp",
|
||||||
|
source: "test",
|
||||||
|
plugin: {
|
||||||
|
id: "whatsapp",
|
||||||
|
meta: {
|
||||||
|
id: "whatsapp",
|
||||||
|
label: "WhatsApp",
|
||||||
|
selectionLabel: "WhatsApp",
|
||||||
|
docsPath: "/channels/whatsapp",
|
||||||
|
blurb: "WhatsApp test stub.",
|
||||||
|
preferSessionLookupForAnnounceTarget: true,
|
||||||
|
},
|
||||||
|
capabilities: { chatTypes: ["direct", "group"] },
|
||||||
|
config: {
|
||||||
|
listAccountIds: () => ["default"],
|
||||||
|
resolveAccount: () => ({}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("sanitizeTextContent", () => {
|
||||||
|
it("strips minimax tool call XML and downgraded markers", () => {
|
||||||
|
const input =
|
||||||
|
'Hello <invoke name="tool">payload</invoke></minimax:tool_call> ' +
|
||||||
|
"[Tool Call: foo (ID: 1)] world";
|
||||||
|
const result = sanitizeTextContent(input).trim();
|
||||||
|
expect(result).toBe("Hello world");
|
||||||
|
expect(result).not.toContain("invoke");
|
||||||
|
expect(result).not.toContain("Tool Call");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("strips thinking tags", () => {
|
||||||
|
const input = "Before <think>secret</think> after";
|
||||||
|
const result = sanitizeTextContent(input).trim();
|
||||||
|
expect(result).toBe("Before after");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("extractAssistantText", () => {
|
||||||
|
it("sanitizes blocks without injecting newlines", () => {
|
||||||
|
const message = {
|
||||||
|
role: "assistant",
|
||||||
|
content: [
|
||||||
|
{ type: "text", text: "Hi " },
|
||||||
|
{ type: "text", text: "<think>secret</think>there" },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
expect(extractAssistantText(message)).toBe("Hi there");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rewrites error-ish assistant text only when the transcript marks it as an error", () => {
|
||||||
|
const message = {
|
||||||
|
role: "assistant",
|
||||||
|
stopReason: "error",
|
||||||
|
errorMessage: "500 Internal Server Error",
|
||||||
|
content: [{ type: "text", text: "500 Internal Server Error" }],
|
||||||
|
};
|
||||||
|
expect(extractAssistantText(message)).toBe("HTTP 500: Internal Server Error");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps normal status text that mentions billing", () => {
|
||||||
|
const message = {
|
||||||
|
role: "assistant",
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: "Firebase downgraded us to the free Spark plan. Check whether billing should be re-enabled.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
expect(extractAssistantText(message)).toBe(
|
||||||
|
"Firebase downgraded us to the free Spark plan. Check whether billing should be re-enabled.",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("resolveAnnounceTarget", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
callGatewayMock.mockReset();
|
||||||
|
await installRegistry();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("derives non-WhatsApp announce targets from the session key", async () => {
|
||||||
|
const { resolveAnnounceTarget } = await loadResolveAnnounceTarget();
|
||||||
|
const target = await resolveAnnounceTarget({
|
||||||
|
sessionKey: "agent:main:discord:group:dev",
|
||||||
|
displayKey: "agent:main:discord:group:dev",
|
||||||
|
});
|
||||||
|
expect(target).toEqual({ channel: "discord", to: "channel:dev" });
|
||||||
|
expect(callGatewayMock).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("hydrates WhatsApp accountId from sessions.list when available", async () => {
|
||||||
|
const { resolveAnnounceTarget } = await loadResolveAnnounceTarget();
|
||||||
|
callGatewayMock.mockResolvedValueOnce({
|
||||||
|
sessions: [
|
||||||
|
{
|
||||||
|
key: "agent:main:whatsapp:group:123@g.us",
|
||||||
|
deliveryContext: {
|
||||||
|
channel: "whatsapp",
|
||||||
|
to: "123@g.us",
|
||||||
|
accountId: "work",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const target = await resolveAnnounceTarget({
|
||||||
|
sessionKey: "agent:main:whatsapp:group:123@g.us",
|
||||||
|
displayKey: "agent:main:whatsapp:group:123@g.us",
|
||||||
|
});
|
||||||
|
expect(target).toEqual({
|
||||||
|
channel: "whatsapp",
|
||||||
|
to: "123@g.us",
|
||||||
|
accountId: "work",
|
||||||
|
});
|
||||||
|
expect(callGatewayMock).toHaveBeenCalledTimes(1);
|
||||||
|
const first = callGatewayMock.mock.calls[0]?.[0] as { method?: string } | undefined;
|
||||||
|
expect(first).toBeDefined();
|
||||||
|
expect(first?.method).toBe("sessions.list");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("sessions_list gating", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
callGatewayMock.mockReset();
|
||||||
|
callGatewayMock.mockResolvedValue({
|
||||||
|
path: "/tmp/sessions.json",
|
||||||
|
sessions: [
|
||||||
|
{ key: "agent:main:main", kind: "direct" },
|
||||||
|
{ key: "agent:other:main", kind: "direct" },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("filters out other agents when tools.agentToAgent.enabled is false", async () => {
|
||||||
|
const tool = createSessionsListTool({ agentSessionKey: "agent:main:main" });
|
||||||
|
const result = await tool.execute("call1", {});
|
||||||
|
expect(result.details).toMatchObject({
|
||||||
|
count: 1,
|
||||||
|
sessions: [{ key: "agent:main:main" }],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("sessions_send gating", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
callGatewayMock.mockReset();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("blocks cross-agent sends when tools.agentToAgent.enabled is false", async () => {
|
||||||
|
const tool = createSessionsSendTool({
|
||||||
|
agentSessionKey: "agent:main:main",
|
||||||
|
agentChannel: "whatsapp",
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await tool.execute("call1", {
|
||||||
|
sessionKey: "agent:other:main",
|
||||||
|
message: "hi",
|
||||||
|
timeoutSeconds: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(callGatewayMock).not.toHaveBeenCalled();
|
||||||
|
expect(result.details).toMatchObject({ status: "forbidden" });
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user