refactor(extensions): dedupe channel config, onboarding, and monitors
This commit is contained in:
@@ -1,5 +1,19 @@
|
||||
import { Type, type Static } from "@sinclair/typebox";
|
||||
|
||||
const tableCreationProperties = {
|
||||
doc_token: Type.String({ description: "Document token" }),
|
||||
parent_block_id: Type.Optional(
|
||||
Type.String({ description: "Parent block ID (default: document root)" }),
|
||||
),
|
||||
row_size: Type.Integer({ description: "Table row count", minimum: 1 }),
|
||||
column_size: Type.Integer({ description: "Table column count", minimum: 1 }),
|
||||
column_width: Type.Optional(
|
||||
Type.Array(Type.Number({ minimum: 1 }), {
|
||||
description: "Column widths in px (length should match column_size)",
|
||||
}),
|
||||
),
|
||||
};
|
||||
|
||||
export const FeishuDocSchema = Type.Union([
|
||||
Type.Object({
|
||||
action: Type.Literal("read"),
|
||||
@@ -59,17 +73,7 @@ export const FeishuDocSchema = Type.Union([
|
||||
// Table creation (explicit structure)
|
||||
Type.Object({
|
||||
action: Type.Literal("create_table"),
|
||||
doc_token: Type.String({ description: "Document token" }),
|
||||
parent_block_id: Type.Optional(
|
||||
Type.String({ description: "Parent block ID (default: document root)" }),
|
||||
),
|
||||
row_size: Type.Integer({ description: "Table row count", minimum: 1 }),
|
||||
column_size: Type.Integer({ description: "Table column count", minimum: 1 }),
|
||||
column_width: Type.Optional(
|
||||
Type.Array(Type.Number({ minimum: 1 }), {
|
||||
description: "Column widths in px (length should match column_size)",
|
||||
}),
|
||||
),
|
||||
...tableCreationProperties,
|
||||
}),
|
||||
Type.Object({
|
||||
action: Type.Literal("write_table_cells"),
|
||||
@@ -82,17 +86,7 @@ export const FeishuDocSchema = Type.Union([
|
||||
}),
|
||||
Type.Object({
|
||||
action: Type.Literal("create_table_with_values"),
|
||||
doc_token: Type.String({ description: "Document token" }),
|
||||
parent_block_id: Type.Optional(
|
||||
Type.String({ description: "Parent block ID (default: document root)" }),
|
||||
),
|
||||
row_size: Type.Integer({ description: "Table row count", minimum: 1 }),
|
||||
column_size: Type.Integer({ description: "Table column count", minimum: 1 }),
|
||||
column_width: Type.Optional(
|
||||
Type.Array(Type.Number({ minimum: 1 }), {
|
||||
description: "Column widths in px (length should match column_size)",
|
||||
}),
|
||||
),
|
||||
...tableCreationProperties,
|
||||
values: Type.Array(Type.Array(Type.String()), {
|
||||
description: "2D matrix values[row][col] to write into table cells",
|
||||
minItems: 1,
|
||||
|
||||
@@ -21,8 +21,8 @@ vi.mock("@larksuiteoapi/node-sdk", () => {
|
||||
});
|
||||
|
||||
describe("feishu_doc account selection", () => {
|
||||
test("uses agentAccountId context when params omit accountId", async () => {
|
||||
const cfg = {
|
||||
function createDocEnabledConfig(): OpenClawPluginApi["config"] {
|
||||
return {
|
||||
channels: {
|
||||
feishu: {
|
||||
enabled: true,
|
||||
@@ -33,6 +33,10 @@ describe("feishu_doc account selection", () => {
|
||||
},
|
||||
},
|
||||
} as OpenClawPluginApi["config"];
|
||||
}
|
||||
|
||||
test("uses agentAccountId context when params omit accountId", async () => {
|
||||
const cfg = createDocEnabledConfig();
|
||||
|
||||
const { api, resolveTool } = createToolFactoryHarness(cfg);
|
||||
registerFeishuDocTools(api);
|
||||
@@ -49,17 +53,7 @@ describe("feishu_doc account selection", () => {
|
||||
});
|
||||
|
||||
test("explicit accountId param overrides agentAccountId context", async () => {
|
||||
const cfg = {
|
||||
channels: {
|
||||
feishu: {
|
||||
enabled: true,
|
||||
accounts: {
|
||||
a: { appId: "app-a", appSecret: "sec-a", tools: { doc: true } },
|
||||
b: { appId: "app-b", appSecret: "sec-b", tools: { doc: true } },
|
||||
},
|
||||
},
|
||||
},
|
||||
} as OpenClawPluginApi["config"];
|
||||
const cfg = createDocEnabledConfig();
|
||||
|
||||
const { api, resolveTool } = createToolFactoryHarness(cfg);
|
||||
registerFeishuDocTools(api);
|
||||
|
||||
@@ -114,6 +114,29 @@ describe("feishu_doc image fetch hardening", () => {
|
||||
scopeListMock.mockResolvedValue({ code: 0, data: { scopes: [] } });
|
||||
});
|
||||
|
||||
function resolveFeishuDocTool(context: Record<string, unknown> = {}) {
|
||||
const registerTool = vi.fn();
|
||||
registerFeishuDocTools({
|
||||
config: {
|
||||
channels: {
|
||||
feishu: {
|
||||
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");
|
||||
expect(tool).toBeDefined();
|
||||
return tool as { execute: (callId: string, params: Record<string, unknown>) => Promise<any> };
|
||||
}
|
||||
|
||||
it("inserts blocks sequentially to preserve document order", async () => {
|
||||
const blocks = [
|
||||
{ block_type: 3, block_id: "h1" },
|
||||
@@ -135,22 +158,7 @@ describe("feishu_doc image fetch hardening", () => {
|
||||
data: { children: [{ block_type: 3, block_id: "h1" }] },
|
||||
});
|
||||
|
||||
const registerTool = vi.fn();
|
||||
registerFeishuDocTools({
|
||||
config: {
|
||||
channels: {
|
||||
feishu: { appId: "app_id", appSecret: "app_secret" },
|
||||
},
|
||||
} as any,
|
||||
logger: { debug: vi.fn(), info: vi.fn() } as any,
|
||||
registerTool,
|
||||
} as any);
|
||||
|
||||
const feishuDocTool = registerTool.mock.calls
|
||||
.map((call) => call[0])
|
||||
.map((tool) => (typeof tool === "function" ? tool({}) : tool))
|
||||
.find((tool) => tool.name === "feishu_doc");
|
||||
expect(feishuDocTool).toBeDefined();
|
||||
const feishuDocTool = resolveFeishuDocTool();
|
||||
|
||||
const result = await feishuDocTool.execute("tool-call", {
|
||||
action: "append",
|
||||
@@ -194,22 +202,7 @@ describe("feishu_doc image fetch hardening", () => {
|
||||
},
|
||||
}));
|
||||
|
||||
const registerTool = vi.fn();
|
||||
registerFeishuDocTools({
|
||||
config: {
|
||||
channels: {
|
||||
feishu: { appId: "app_id", appSecret: "app_secret" },
|
||||
},
|
||||
} as any,
|
||||
logger: { debug: vi.fn(), info: vi.fn() } as any,
|
||||
registerTool,
|
||||
} as any);
|
||||
|
||||
const feishuDocTool = registerTool.mock.calls
|
||||
.map((call) => call[0])
|
||||
.map((tool) => (typeof tool === "function" ? tool({}) : tool))
|
||||
.find((tool) => tool.name === "feishu_doc");
|
||||
expect(feishuDocTool).toBeDefined();
|
||||
const feishuDocTool = resolveFeishuDocTool();
|
||||
|
||||
const longMarkdown = Array.from(
|
||||
{ length: 120 },
|
||||
@@ -254,22 +247,7 @@ describe("feishu_doc image fetch hardening", () => {
|
||||
data: { children: data.children },
|
||||
}));
|
||||
|
||||
const registerTool = vi.fn();
|
||||
registerFeishuDocTools({
|
||||
config: {
|
||||
channels: {
|
||||
feishu: { appId: "app_id", appSecret: "app_secret" },
|
||||
},
|
||||
} as any,
|
||||
logger: { debug: vi.fn(), info: vi.fn() } as any,
|
||||
registerTool,
|
||||
} as any);
|
||||
|
||||
const feishuDocTool = registerTool.mock.calls
|
||||
.map((call) => call[0])
|
||||
.map((tool) => (typeof tool === "function" ? tool({}) : tool))
|
||||
.find((tool) => tool.name === "feishu_doc");
|
||||
expect(feishuDocTool).toBeDefined();
|
||||
const feishuDocTool = resolveFeishuDocTool();
|
||||
|
||||
const fencedMarkdown = [
|
||||
"## Section",
|
||||
@@ -306,25 +284,7 @@ describe("feishu_doc image fetch hardening", () => {
|
||||
new Error("Blocked: resolves to private/internal IP address"),
|
||||
);
|
||||
|
||||
const registerTool = vi.fn();
|
||||
registerFeishuDocTools({
|
||||
config: {
|
||||
channels: {
|
||||
feishu: {
|
||||
appId: "app_id",
|
||||
appSecret: "app_secret",
|
||||
},
|
||||
},
|
||||
} as any,
|
||||
logger: { debug: vi.fn(), info: vi.fn() } as any,
|
||||
registerTool,
|
||||
} as any);
|
||||
|
||||
const feishuDocTool = registerTool.mock.calls
|
||||
.map((call) => call[0])
|
||||
.map((tool) => (typeof tool === "function" ? tool({}) : tool))
|
||||
.find((tool) => tool.name === "feishu_doc");
|
||||
expect(feishuDocTool).toBeDefined();
|
||||
const feishuDocTool = resolveFeishuDocTool();
|
||||
|
||||
const result = await feishuDocTool.execute("tool-call", {
|
||||
action: "write",
|
||||
@@ -341,29 +301,10 @@ describe("feishu_doc image fetch hardening", () => {
|
||||
});
|
||||
|
||||
it("create grants permission only to trusted Feishu requester", async () => {
|
||||
const registerTool = vi.fn();
|
||||
registerFeishuDocTools({
|
||||
config: {
|
||||
channels: {
|
||||
feishu: {
|
||||
appId: "app_id",
|
||||
appSecret: "app_secret",
|
||||
},
|
||||
},
|
||||
} as any,
|
||||
logger: { debug: vi.fn(), info: vi.fn() } as any,
|
||||
registerTool,
|
||||
} as any);
|
||||
|
||||
const feishuDocTool = registerTool.mock.calls
|
||||
.map((call) => call[0])
|
||||
.map((tool) =>
|
||||
typeof tool === "function"
|
||||
? tool({ messageChannel: "feishu", requesterSenderId: "ou_123" })
|
||||
: tool,
|
||||
)
|
||||
.find((tool) => tool.name === "feishu_doc");
|
||||
expect(feishuDocTool).toBeDefined();
|
||||
const feishuDocTool = resolveFeishuDocTool({
|
||||
messageChannel: "feishu",
|
||||
requesterSenderId: "ou_123",
|
||||
});
|
||||
|
||||
const result = await feishuDocTool.execute("tool-call", {
|
||||
action: "create",
|
||||
@@ -386,25 +327,9 @@ describe("feishu_doc image fetch hardening", () => {
|
||||
});
|
||||
|
||||
it("create skips requester grant when trusted requester identity is unavailable", async () => {
|
||||
const registerTool = vi.fn();
|
||||
registerFeishuDocTools({
|
||||
config: {
|
||||
channels: {
|
||||
feishu: {
|
||||
appId: "app_id",
|
||||
appSecret: "app_secret",
|
||||
},
|
||||
},
|
||||
} as any,
|
||||
logger: { debug: vi.fn(), info: vi.fn() } as any,
|
||||
registerTool,
|
||||
} as any);
|
||||
|
||||
const feishuDocTool = registerTool.mock.calls
|
||||
.map((call) => call[0])
|
||||
.map((tool) => (typeof tool === "function" ? tool({ messageChannel: "feishu" }) : tool))
|
||||
.find((tool) => tool.name === "feishu_doc");
|
||||
expect(feishuDocTool).toBeDefined();
|
||||
const feishuDocTool = resolveFeishuDocTool({
|
||||
messageChannel: "feishu",
|
||||
});
|
||||
|
||||
const result = await feishuDocTool.execute("tool-call", {
|
||||
action: "create",
|
||||
@@ -417,29 +342,10 @@ describe("feishu_doc image fetch hardening", () => {
|
||||
});
|
||||
|
||||
it("create never grants permissions when grant_to_requester is false", async () => {
|
||||
const registerTool = vi.fn();
|
||||
registerFeishuDocTools({
|
||||
config: {
|
||||
channels: {
|
||||
feishu: {
|
||||
appId: "app_id",
|
||||
appSecret: "app_secret",
|
||||
},
|
||||
},
|
||||
} as any,
|
||||
logger: { debug: vi.fn(), info: vi.fn() } as any,
|
||||
registerTool,
|
||||
} as any);
|
||||
|
||||
const feishuDocTool = registerTool.mock.calls
|
||||
.map((call) => call[0])
|
||||
.map((tool) =>
|
||||
typeof tool === "function"
|
||||
? tool({ messageChannel: "feishu", requesterSenderId: "ou_123" })
|
||||
: tool,
|
||||
)
|
||||
.find((tool) => tool.name === "feishu_doc");
|
||||
expect(feishuDocTool).toBeDefined();
|
||||
const feishuDocTool = resolveFeishuDocTool({
|
||||
messageChannel: "feishu",
|
||||
requesterSenderId: "ou_123",
|
||||
});
|
||||
|
||||
const result = await feishuDocTool.execute("tool-call", {
|
||||
action: "create",
|
||||
@@ -457,25 +363,7 @@ describe("feishu_doc image fetch hardening", () => {
|
||||
data: { document: { title: "Created Doc" } },
|
||||
});
|
||||
|
||||
const registerTool = vi.fn();
|
||||
registerFeishuDocTools({
|
||||
config: {
|
||||
channels: {
|
||||
feishu: {
|
||||
appId: "app_id",
|
||||
appSecret: "app_secret",
|
||||
},
|
||||
},
|
||||
} as any,
|
||||
logger: { debug: vi.fn(), info: vi.fn() } as any,
|
||||
registerTool,
|
||||
} as any);
|
||||
|
||||
const feishuDocTool = registerTool.mock.calls
|
||||
.map((call) => call[0])
|
||||
.map((tool) => (typeof tool === "function" ? tool({}) : tool))
|
||||
.find((tool) => tool.name === "feishu_doc");
|
||||
expect(feishuDocTool).toBeDefined();
|
||||
const feishuDocTool = resolveFeishuDocTool();
|
||||
|
||||
const result = await feishuDocTool.execute("tool-call", {
|
||||
action: "create",
|
||||
@@ -496,25 +384,7 @@ describe("feishu_doc image fetch hardening", () => {
|
||||
const localPath = join(tmpdir(), `feishu-docx-upload-${Date.now()}.txt`);
|
||||
await fs.writeFile(localPath, "hello from local file", "utf8");
|
||||
|
||||
const registerTool = vi.fn();
|
||||
registerFeishuDocTools({
|
||||
config: {
|
||||
channels: {
|
||||
feishu: {
|
||||
appId: "app_id",
|
||||
appSecret: "app_secret",
|
||||
},
|
||||
},
|
||||
} as any,
|
||||
logger: { debug: vi.fn(), info: vi.fn() } as any,
|
||||
registerTool,
|
||||
} as any);
|
||||
|
||||
const feishuDocTool = registerTool.mock.calls
|
||||
.map((call) => call[0])
|
||||
.map((tool) => (typeof tool === "function" ? tool({}) : tool))
|
||||
.find((tool) => tool.name === "feishu_doc");
|
||||
expect(feishuDocTool).toBeDefined();
|
||||
const feishuDocTool = resolveFeishuDocTool();
|
||||
|
||||
const result = await feishuDocTool.execute("tool-call", {
|
||||
action: "upload_file",
|
||||
@@ -557,25 +427,7 @@ describe("feishu_doc image fetch hardening", () => {
|
||||
await fs.writeFile(localPath, "hello from local file", "utf8");
|
||||
|
||||
try {
|
||||
const registerTool = vi.fn();
|
||||
registerFeishuDocTools({
|
||||
config: {
|
||||
channels: {
|
||||
feishu: {
|
||||
appId: "app_id",
|
||||
appSecret: "app_secret",
|
||||
},
|
||||
},
|
||||
} as any,
|
||||
logger: { debug: vi.fn(), info: vi.fn() } as any,
|
||||
registerTool,
|
||||
} as any);
|
||||
|
||||
const feishuDocTool = registerTool.mock.calls
|
||||
.map((call) => call[0])
|
||||
.map((tool) => (typeof tool === "function" ? tool({}) : tool))
|
||||
.find((tool) => tool.name === "feishu_doc");
|
||||
expect(feishuDocTool).toBeDefined();
|
||||
const feishuDocTool = resolveFeishuDocTool();
|
||||
|
||||
const result = await feishuDocTool.execute("tool-call", {
|
||||
action: "upload_file",
|
||||
|
||||
@@ -1,18 +1,7 @@
|
||||
import type { ClawdbotConfig } from "openclaw/plugin-sdk";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const probeFeishuMock = vi.hoisted(() => vi.fn());
|
||||
|
||||
vi.mock("./probe.js", () => ({
|
||||
probeFeishu: probeFeishuMock,
|
||||
}));
|
||||
|
||||
vi.mock("./client.js", () => ({
|
||||
createFeishuWSClient: vi.fn(() => ({ start: vi.fn() })),
|
||||
createEventDispatcher: vi.fn(() => ({ register: vi.fn() })),
|
||||
}));
|
||||
|
||||
import { monitorFeishuProvider, stopFeishuMonitor } from "./monitor.js";
|
||||
import { probeFeishuMock } from "./monitor.test-mocks.js";
|
||||
|
||||
function buildMultiAccountWebsocketConfig(accountIds: string[]): ClawdbotConfig {
|
||||
return {
|
||||
|
||||
12
extensions/feishu/src/monitor.test-mocks.ts
Normal file
12
extensions/feishu/src/monitor.test-mocks.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { vi } from "vitest";
|
||||
|
||||
export const probeFeishuMock = vi.hoisted(() => vi.fn());
|
||||
|
||||
vi.mock("./probe.js", () => ({
|
||||
probeFeishu: probeFeishuMock,
|
||||
}));
|
||||
|
||||
vi.mock("./client.js", () => ({
|
||||
createFeishuWSClient: vi.fn(() => ({ start: vi.fn() })),
|
||||
createEventDispatcher: vi.fn(() => ({ register: vi.fn() })),
|
||||
}));
|
||||
@@ -2,8 +2,7 @@ import { createServer } from "node:http";
|
||||
import type { AddressInfo } from "node:net";
|
||||
import type { ClawdbotConfig } from "openclaw/plugin-sdk";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const probeFeishuMock = vi.hoisted(() => vi.fn());
|
||||
import { probeFeishuMock } from "./monitor.test-mocks.js";
|
||||
|
||||
vi.mock("@larksuiteoapi/node-sdk", () => ({
|
||||
adaptDefault: vi.fn(
|
||||
@@ -14,15 +13,6 @@ vi.mock("@larksuiteoapi/node-sdk", () => ({
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock("./probe.js", () => ({
|
||||
probeFeishu: probeFeishuMock,
|
||||
}));
|
||||
|
||||
vi.mock("./client.js", () => ({
|
||||
createFeishuWSClient: vi.fn(() => ({ start: vi.fn() })),
|
||||
createEventDispatcher: vi.fn(() => ({ register: vi.fn() })),
|
||||
}));
|
||||
|
||||
import {
|
||||
clearFeishuWebhookRateLimitStateForTest,
|
||||
getFeishuWebhookRateLimitStateSizeForTest,
|
||||
|
||||
Reference in New Issue
Block a user