Files
openclaw/extensions/feishu/src/tool-account-routing.test.ts
2026-03-27 14:38:40 +00:00

184 lines
6.2 KiB
TypeScript

import { beforeEach, describe, expect, test, vi } from "vitest";
import type { OpenClawPluginApi } from "../runtime-api.js";
import { createToolFactoryHarness } from "./tool-factory-test-harness.js";
const createFeishuClientMock = vi.fn((account: { appId?: string } | undefined) => ({
__appId: account?.appId,
}));
vi.mock("./client.js", () => ({
createFeishuClient: (account: { appId?: string } | undefined) => createFeishuClientMock(account),
}));
let registerFeishuBitableTools: typeof import("./bitable.js").registerFeishuBitableTools;
let registerFeishuDriveTools: typeof import("./drive.js").registerFeishuDriveTools;
let registerFeishuPermTools: typeof import("./perm.js").registerFeishuPermTools;
let registerFeishuWikiTools: typeof import("./wiki.js").registerFeishuWikiTools;
function createConfig(params: {
toolsA?: {
wiki?: boolean;
drive?: boolean;
perm?: boolean;
};
toolsB?: {
wiki?: boolean;
drive?: boolean;
perm?: boolean;
};
defaultAccount?: string;
}): OpenClawPluginApi["config"] {
return {
channels: {
feishu: {
enabled: true,
defaultAccount: params.defaultAccount,
accounts: {
a: {
appId: "app-a",
appSecret: "sec-a", // pragma: allowlist secret
tools: params.toolsA,
},
b: {
appId: "app-b",
appSecret: "sec-b", // pragma: allowlist secret
tools: params.toolsB,
},
},
},
},
} as OpenClawPluginApi["config"];
}
describe("feishu tool account routing", () => {
beforeEach(async () => {
vi.resetModules();
({ registerFeishuBitableTools, registerFeishuDriveTools, registerFeishuPermTools } =
await import("./bitable.js").then(async ({ registerFeishuBitableTools }) => ({
registerFeishuBitableTools,
...(await import("./drive.js")),
...(await import("./perm.js")),
...(await import("./wiki.js")),
})));
({ registerFeishuWikiTools } = await import("./wiki.js"));
vi.clearAllMocks();
});
test("wiki tool registers when first account disables it and routes to agentAccountId", async () => {
const { api, resolveTool } = createToolFactoryHarness(
createConfig({
toolsA: { wiki: false },
toolsB: { wiki: true },
}),
);
registerFeishuWikiTools(api);
const tool = resolveTool("feishu_wiki", { agentAccountId: "b" });
await tool.execute("call", { action: "search" });
expect(createFeishuClientMock.mock.calls.at(-1)?.[0]?.appId).toBe("app-b");
});
test("wiki tool prefers configured defaultAccount over inherited default account context", async () => {
const { api, resolveTool } = createToolFactoryHarness(
createConfig({
defaultAccount: "b",
toolsA: { wiki: true },
toolsB: { wiki: true },
}),
);
registerFeishuWikiTools(api);
const tool = resolveTool("feishu_wiki", { agentAccountId: "a" });
await tool.execute("call", { action: "search" });
expect(createFeishuClientMock.mock.calls.at(-1)?.[0]?.appId).toBe("app-b");
});
test("drive tool registers when first account disables it and routes to agentAccountId", async () => {
const { api, resolveTool } = createToolFactoryHarness(
createConfig({
toolsA: { drive: false },
toolsB: { drive: true },
}),
);
registerFeishuDriveTools(api);
const tool = resolveTool("feishu_drive", { agentAccountId: "b" });
await tool.execute("call", { action: "unknown_action" });
expect(createFeishuClientMock.mock.calls.at(-1)?.[0]?.appId).toBe("app-b");
});
test("perm tool registers when only second account enables it and routes to agentAccountId", async () => {
const { api, resolveTool } = createToolFactoryHarness(
createConfig({
toolsA: { perm: false },
toolsB: { perm: true },
}),
);
registerFeishuPermTools(api);
const tool = resolveTool("feishu_perm", { agentAccountId: "b" });
await tool.execute("call", { action: "unknown_action" });
expect(createFeishuClientMock.mock.calls.at(-1)?.[0]?.appId).toBe("app-b");
});
test("bitable tool routes to agentAccountId and allows explicit accountId override", async () => {
const { api, resolveTool } = createToolFactoryHarness(createConfig({}));
registerFeishuBitableTools(api);
const tool = resolveTool("feishu_bitable_get_meta", { agentAccountId: "b" });
await tool.execute("call-ctx", { url: "invalid-url" });
await tool.execute("call-override", { url: "invalid-url", accountId: "a" });
expect(createFeishuClientMock.mock.calls[0]?.[0]?.appId).toBe("app-b");
expect(createFeishuClientMock.mock.calls[1]?.[0]?.appId).toBe("app-a");
});
test("falls back to the configured Feishu default selection when agentAccountId is not a real account", async () => {
const { api, resolveTool } = createToolFactoryHarness(
createConfig({
toolsA: { wiki: true },
toolsB: { wiki: true },
}),
);
registerFeishuWikiTools(api);
const tool = resolveTool("feishu_wiki", { agentAccountId: "agent-spawner" });
await tool.execute("call", { action: "search" });
expect(createFeishuClientMock.mock.calls.at(-1)?.[0]?.appId).toBe("app-a");
});
test("does not silently fall back when the contextual account is real but uses non-env SecretRefs", async () => {
const { api, resolveTool } = createToolFactoryHarness({
channels: {
feishu: {
enabled: true,
accounts: {
a: {
appId: "app-a",
appSecret: "sec-a", // pragma: allowlist secret
tools: { wiki: true },
},
b: {
appId: "app-b",
appSecret: { source: "file", provider: "default", id: "feishu/b-secret" },
tools: { wiki: true },
} as never,
},
},
},
} as OpenClawPluginApi["config"]);
registerFeishuWikiTools(api);
const tool = resolveTool("feishu_wiki", { agentAccountId: "b" });
const result = await tool.execute("call", { action: "search" });
expect(createFeishuClientMock).not.toHaveBeenCalled();
expect(String(result.details.error ?? "")).toContain("unresolved SecretRef");
});
});