mirror of https://github.com/openclaw/openclaw.git
test(feishu): type tool harness fixtures
This commit is contained in:
parent
1042710e3b
commit
6ad50ce474
|
|
@ -1,4 +1,6 @@
|
|||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { createTestPluginApi } from "../../../test/helpers/extensions/plugin-api.js";
|
||||
import type { OpenClawPluginApi } from "../runtime-api.js";
|
||||
import { registerFeishuChatTools } from "./chat.js";
|
||||
|
||||
const createFeishuClientMock = vi.hoisted(() => vi.fn());
|
||||
|
|
@ -11,6 +13,21 @@ vi.mock("./client.js", () => ({
|
|||
}));
|
||||
|
||||
describe("registerFeishuChatTools", () => {
|
||||
function createChatToolApi(params: {
|
||||
config: OpenClawPluginApi["config"];
|
||||
registerTool: OpenClawPluginApi["registerTool"];
|
||||
}): OpenClawPluginApi {
|
||||
return createTestPluginApi({
|
||||
id: "feishu-test",
|
||||
name: "Feishu Test",
|
||||
source: "local",
|
||||
config: params.config,
|
||||
runtime: { log: vi.fn(), error: vi.fn() },
|
||||
logger: { debug: vi.fn(), info: vi.fn() },
|
||||
registerTool: params.registerTool,
|
||||
});
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
createFeishuClientMock.mockReturnValue({
|
||||
|
|
@ -26,20 +43,21 @@ describe("registerFeishuChatTools", () => {
|
|||
|
||||
it("registers feishu_chat and handles info/members actions", async () => {
|
||||
const registerTool = vi.fn();
|
||||
registerFeishuChatTools({
|
||||
config: {
|
||||
channels: {
|
||||
feishu: {
|
||||
enabled: true,
|
||||
appId: "app_id",
|
||||
appSecret: "app_secret", // pragma: allowlist secret
|
||||
tools: { chat: true },
|
||||
registerFeishuChatTools(
|
||||
createChatToolApi({
|
||||
config: {
|
||||
channels: {
|
||||
feishu: {
|
||||
enabled: true,
|
||||
appId: "app_id",
|
||||
appSecret: "app_secret", // pragma: allowlist secret
|
||||
tools: { chat: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
} as any,
|
||||
logger: { debug: vi.fn(), info: vi.fn() } as any,
|
||||
registerTool,
|
||||
} as any);
|
||||
registerTool,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(registerTool).toHaveBeenCalledTimes(1);
|
||||
const tool = registerTool.mock.calls[0]?.[0];
|
||||
|
|
@ -98,20 +116,21 @@ describe("registerFeishuChatTools", () => {
|
|||
|
||||
it("skips registration when chat tool is disabled", () => {
|
||||
const registerTool = vi.fn();
|
||||
registerFeishuChatTools({
|
||||
config: {
|
||||
channels: {
|
||||
feishu: {
|
||||
enabled: true,
|
||||
appId: "app_id",
|
||||
appSecret: "app_secret", // pragma: allowlist secret
|
||||
tools: { chat: false },
|
||||
registerFeishuChatTools(
|
||||
createChatToolApi({
|
||||
config: {
|
||||
channels: {
|
||||
feishu: {
|
||||
enabled: true,
|
||||
appId: "app_id",
|
||||
appSecret: "app_secret", // pragma: allowlist secret
|
||||
tools: { chat: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
} as any,
|
||||
logger: { debug: vi.fn(), info: vi.fn() } as any,
|
||||
registerTool,
|
||||
} as any);
|
||||
registerTool,
|
||||
}),
|
||||
);
|
||||
expect(registerTool).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { createTestPluginApi } from "../../../test/helpers/extensions/plugin-api.js";
|
||||
import type { OpenClawPluginApi } from "../runtime-api.js";
|
||||
import type { FeishuConfig, ResolvedFeishuAccount } from "./types.js";
|
||||
|
||||
|
|
@ -99,12 +100,24 @@ const baseAccount: ResolvedFeishuAccount = {
|
|||
appId: "app_123",
|
||||
appSecret: "secret_123", // pragma: allowlist secret
|
||||
domain: "feishu",
|
||||
config: {} as FeishuConfig,
|
||||
config: {},
|
||||
};
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null;
|
||||
}
|
||||
|
||||
function readCallOptions(
|
||||
mock: { mock: { calls: unknown[][] } },
|
||||
index = -1,
|
||||
): Record<string, unknown> {
|
||||
const call = index < 0 ? mock.mock.calls.at(index)?.[0] : mock.mock.calls[index]?.[0];
|
||||
return isRecord(call) ? call : {};
|
||||
}
|
||||
|
||||
function firstWsClientOptions(): { agent?: unknown } {
|
||||
const calls = wsClientCtorMock.mock.calls as unknown as Array<[options: { agent?: unknown }]>;
|
||||
return calls[0]?.[0] ?? {};
|
||||
const options = readCallOptions(wsClientCtorMock, 0);
|
||||
return { agent: options.agent };
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
|
|
@ -179,11 +192,8 @@ afterEach(() => {
|
|||
|
||||
describe("createFeishuClient HTTP timeout", () => {
|
||||
const getLastClientHttpInstance = () => {
|
||||
const calls = clientCtorMock.mock.calls as unknown as Array<[options: unknown]>;
|
||||
const lastCall = calls[calls.length - 1]?.[0] as
|
||||
| { httpInstance?: { get: (...args: unknown[]) => Promise<unknown> } }
|
||||
| undefined;
|
||||
return lastCall?.httpInstance;
|
||||
const httpInstance = readCallOptions(clientCtorMock).httpInstance;
|
||||
return isRecord(httpInstance) ? httpInstance : undefined;
|
||||
};
|
||||
|
||||
const expectGetCallTimeout = async (timeout: number) => {
|
||||
|
|
@ -199,22 +209,16 @@ describe("createFeishuClient HTTP timeout", () => {
|
|||
it("passes a custom httpInstance with default timeout to Lark.Client", () => {
|
||||
createFeishuClient({ appId: "app_1", appSecret: "secret_1", accountId: "timeout-test" }); // pragma: allowlist secret
|
||||
|
||||
const calls = clientCtorMock.mock.calls as unknown as Array<[options: unknown]>;
|
||||
const lastCall = calls[calls.length - 1]?.[0] as { httpInstance?: unknown } | undefined;
|
||||
expect(lastCall?.httpInstance).toBeDefined();
|
||||
expect(readCallOptions(clientCtorMock).httpInstance).toBeDefined();
|
||||
});
|
||||
|
||||
it("injects default timeout into HTTP request options", async () => {
|
||||
createFeishuClient({ appId: "app_2", appSecret: "secret_2", accountId: "timeout-inject" }); // pragma: allowlist secret
|
||||
|
||||
const calls = clientCtorMock.mock.calls as unknown as Array<[options: unknown]>;
|
||||
const lastCall = calls[calls.length - 1]?.[0] as
|
||||
| { httpInstance: { post: (...args: unknown[]) => Promise<unknown> } }
|
||||
| undefined;
|
||||
const httpInstance = lastCall?.httpInstance;
|
||||
const httpInstance = getLastClientHttpInstance();
|
||||
|
||||
expect(httpInstance).toBeDefined();
|
||||
await httpInstance?.post(
|
||||
await httpInstance?.post?.(
|
||||
"https://example.com/api",
|
||||
{ data: 1 },
|
||||
{ headers: { "X-Custom": "yes" } },
|
||||
|
|
@ -230,14 +234,10 @@ describe("createFeishuClient HTTP timeout", () => {
|
|||
it("allows explicit timeout override per-request", async () => {
|
||||
createFeishuClient({ appId: "app_3", appSecret: "secret_3", accountId: "timeout-override" }); // pragma: allowlist secret
|
||||
|
||||
const calls = clientCtorMock.mock.calls as unknown as Array<[options: unknown]>;
|
||||
const lastCall = calls[calls.length - 1]?.[0] as
|
||||
| { httpInstance: { get: (...args: unknown[]) => Promise<unknown> } }
|
||||
| undefined;
|
||||
const httpInstance = lastCall?.httpInstance;
|
||||
const httpInstance = getLastClientHttpInstance();
|
||||
|
||||
expect(httpInstance).toBeDefined();
|
||||
await httpInstance?.get("https://example.com/api", { timeout: 5_000 });
|
||||
await httpInstance?.get?.("https://example.com/api", { timeout: 5_000 });
|
||||
|
||||
expect(mockBaseHttpInstance.get).toHaveBeenCalledWith(
|
||||
"https://example.com/api",
|
||||
|
|
@ -320,14 +320,10 @@ describe("createFeishuClient HTTP timeout", () => {
|
|||
config: { httpTimeoutMs: 45_000 },
|
||||
});
|
||||
|
||||
const calls = clientCtorMock.mock.calls as unknown as Array<[options: unknown]>;
|
||||
expect(calls.length).toBe(2);
|
||||
|
||||
const lastCall = calls[calls.length - 1]?.[0] as
|
||||
| { httpInstance: { get: (...args: unknown[]) => Promise<unknown> } }
|
||||
| undefined;
|
||||
expect(lastCall?.httpInstance).toBeDefined();
|
||||
await lastCall?.httpInstance.get("https://example.com/api");
|
||||
expect(clientCtorMock.mock.calls.length).toBe(2);
|
||||
const httpInstance = getLastClientHttpInstance();
|
||||
expect(httpInstance).toBeDefined();
|
||||
await httpInstance?.get?.("https://example.com/api");
|
||||
|
||||
expect(mockBaseHttpInstance.get).toHaveBeenCalledWith(
|
||||
"https://example.com/api",
|
||||
|
|
@ -340,13 +336,15 @@ describe("feishu plugin register", () => {
|
|||
it("registers the Feishu channel, tools, and subagent hooks", async () => {
|
||||
const { default: plugin } = await import("../index.js");
|
||||
const registerChannel = vi.fn();
|
||||
const api = {
|
||||
const api = createTestPluginApi({
|
||||
id: "feishu-test",
|
||||
name: "Feishu Test",
|
||||
source: "local",
|
||||
runtime: { log: vi.fn() },
|
||||
registerChannel,
|
||||
on: vi.fn(),
|
||||
config: {},
|
||||
registrationMode: "full",
|
||||
} as unknown as OpenClawPluginApi;
|
||||
registerChannel,
|
||||
});
|
||||
|
||||
plugin.register(api);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,23 @@
|
|||
import type * as Lark from "@larksuiteoapi/node-sdk";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { BATCH_SIZE, insertBlocksInBatches } from "./docx-batch-insert.js";
|
||||
import type { FeishuDocxBlock } from "./docx-types.js";
|
||||
|
||||
type DocxDescendantCreate = Lark.Client["docx"]["documentBlockDescendant"]["create"];
|
||||
type DocxDescendantCreateParams = Parameters<DocxDescendantCreate>[0];
|
||||
type DocxDescendantCreateResponse = Awaited<ReturnType<DocxDescendantCreate>>;
|
||||
|
||||
function createDocxDescendantClient(
|
||||
create: (params: DocxDescendantCreateParams) => Promise<DocxDescendantCreateResponse>,
|
||||
): Pick<Lark.Client, "docx"> {
|
||||
return {
|
||||
docx: {
|
||||
documentBlockDescendant: {
|
||||
create,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createCountingIterable<T>(values: T[]) {
|
||||
let iterations = 0;
|
||||
|
|
@ -28,18 +46,12 @@ describe("insertBlocksInBatches", () => {
|
|||
children: data.children_id.map((id) => ({ block_id: id })),
|
||||
},
|
||||
}));
|
||||
const client = {
|
||||
docx: {
|
||||
documentBlockDescendant: {
|
||||
create: createMock,
|
||||
},
|
||||
},
|
||||
} as any;
|
||||
const client = createDocxDescendantClient(createMock);
|
||||
|
||||
const result = await insertBlocksInBatches(
|
||||
client,
|
||||
"doc_1",
|
||||
counting.values as any[],
|
||||
Array.from(counting.values),
|
||||
blocks.map((block) => block.block_id),
|
||||
);
|
||||
|
||||
|
|
@ -63,21 +75,15 @@ describe("insertBlocksInBatches", () => {
|
|||
},
|
||||
}),
|
||||
);
|
||||
const client = {
|
||||
docx: {
|
||||
documentBlockDescendant: {
|
||||
create: createMock,
|
||||
},
|
||||
},
|
||||
} as any;
|
||||
const blocks = [
|
||||
const client = createDocxDescendantClient(createMock);
|
||||
const blocks: FeishuDocxBlock[] = [
|
||||
{ block_id: "root_a", block_type: 1, children: ["child_a"] },
|
||||
{ block_id: "child_a", block_type: 2 },
|
||||
{ block_id: "root_b", block_type: 1, children: ["child_b"] },
|
||||
{ block_id: "child_b", block_type: 2 },
|
||||
];
|
||||
|
||||
await insertBlocksInBatches(client, "doc_1", blocks as any[], ["root_a", "root_b"]);
|
||||
await insertBlocksInBatches(client, "doc_1", blocks, ["root_a", "root_b"]);
|
||||
|
||||
expect(createMock).toHaveBeenCalledTimes(1);
|
||||
expect(createMock.mock.calls[0]?.[0]?.data.children_id).toEqual(["root_a", "root_b"]);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { createToolFactoryHarness, type ToolLike } from "./tool-factory-test-harness.js";
|
||||
|
||||
const createFeishuClientMock = vi.hoisted(() => vi.fn());
|
||||
const fetchRemoteMediaMock = vi.hoisted(() => vi.fn());
|
||||
|
|
@ -115,26 +116,19 @@ describe("feishu_doc image fetch hardening", () => {
|
|||
});
|
||||
|
||||
function resolveFeishuDocTool(context: Record<string, unknown> = {}) {
|
||||
const registerTool = vi.fn();
|
||||
registerFeishuDocTools({
|
||||
config: {
|
||||
channels: {
|
||||
feishu: {
|
||||
appId: "app_id",
|
||||
appSecret: "app_secret",
|
||||
},
|
||||
const harness = createToolFactoryHarness({
|
||||
channels: {
|
||||
feishu: {
|
||||
enabled: true,
|
||||
appId: "app_id",
|
||||
appSecret: "app_secret",
|
||||
},
|
||||
} as any,
|
||||
logger: { debug: vi.fn(), info: vi.fn() } as any,
|
||||
registerTool,
|
||||
} as any);
|
||||
|
||||
const tool = registerTool.mock.calls
|
||||
.map((call) => call[0])
|
||||
.map((candidate) => (typeof candidate === "function" ? candidate(context) : candidate))
|
||||
.find((candidate) => candidate.name === "feishu_doc");
|
||||
},
|
||||
});
|
||||
registerFeishuDocTools(harness.api);
|
||||
const tool = harness.resolveTool("feishu_doc", context);
|
||||
expect(tool).toBeDefined();
|
||||
return tool as { execute: (callId: string, params: Record<string, unknown>) => Promise<any> };
|
||||
return tool as ToolLike;
|
||||
}
|
||||
|
||||
it("inserts blocks sequentially to preserve document order", async () => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue