PR: Feishu Plugin - Auto-grant document permissions to requesting user (openclaw#28295) thanks @zhoulongchao77

Verified:
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: zhoulongchao77 <65058500+zhoulongchao77@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
This commit is contained in:
zhoulc777
2026-02-28 07:34:18 +08:00
committed by GitHub
parent fa5e71d1ae
commit bf9585d056
4 changed files with 168 additions and 4 deletions

View File

@@ -21,9 +21,11 @@ import { registerFeishuDocTools } from "./docx.js";
describe("feishu_doc image fetch hardening", () => {
const convertMock = vi.hoisted(() => vi.fn());
const documentCreateMock = vi.hoisted(() => vi.fn());
const blockListMock = vi.hoisted(() => vi.fn());
const blockChildrenCreateMock = vi.hoisted(() => vi.fn());
const driveUploadAllMock = vi.hoisted(() => vi.fn());
const permissionMemberCreateMock = vi.hoisted(() => vi.fn());
const blockPatchMock = vi.hoisted(() => vi.fn());
const scopeListMock = vi.hoisted(() => vi.fn());
@@ -34,6 +36,7 @@ describe("feishu_doc image fetch hardening", () => {
docx: {
document: {
convert: convertMock,
create: documentCreateMock,
},
documentBlock: {
list: blockListMock,
@@ -47,6 +50,9 @@ describe("feishu_doc image fetch hardening", () => {
media: {
uploadAll: driveUploadAllMock,
},
permissionMember: {
create: permissionMemberCreateMock,
},
},
application: {
scope: {
@@ -78,6 +84,11 @@ describe("feishu_doc image fetch hardening", () => {
});
driveUploadAllMock.mockResolvedValue({ file_token: "token_1" });
documentCreateMock.mockResolvedValue({
code: 0,
data: { document: { document_id: "doc_created", title: "Created Doc" } },
});
permissionMemberCreateMock.mockResolvedValue({ code: 0 });
blockPatchMock.mockResolvedValue({ code: 0 });
scopeListMock.mockResolvedValue({ code: 0, data: { scopes: [] } });
});
@@ -121,4 +132,107 @@ describe("feishu_doc image fetch hardening", () => {
expect(consoleErrorSpy).toHaveBeenCalled();
consoleErrorSpy.mockRestore();
});
it("reports owner permission details when grant succeeds", 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({}) : tool))
.find((tool) => tool.name === "feishu_doc");
expect(feishuDocTool).toBeDefined();
const result = await feishuDocTool.execute("tool-call", {
action: "create",
title: "Demo",
owner_open_id: "ou_123",
owner_perm_type: "edit",
});
expect(permissionMemberCreateMock).toHaveBeenCalled();
expect(result.details.owner_permission_added).toBe(true);
expect(result.details.owner_open_id).toBe("ou_123");
expect(result.details.owner_perm_type).toBe("edit");
});
it("does not report owner permission details when grant fails", async () => {
const consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
permissionMemberCreateMock.mockRejectedValueOnce(new Error("permission denied"));
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 result = await feishuDocTool.execute("tool-call", {
action: "create",
title: "Demo",
owner_open_id: "ou_123",
owner_perm_type: "edit",
});
expect(permissionMemberCreateMock).toHaveBeenCalled();
expect(result.details.owner_permission_added).toBeUndefined();
expect(result.details.owner_open_id).toBeUndefined();
expect(result.details.owner_perm_type).toBeUndefined();
expect(consoleWarnSpy).toHaveBeenCalled();
consoleWarnSpy.mockRestore();
});
it("skips permission grant when owner_open_id is omitted", 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({}) : tool))
.find((tool) => tool.name === "feishu_doc");
expect(feishuDocTool).toBeDefined();
const result = await feishuDocTool.execute("tool-call", {
action: "create",
title: "Demo",
});
expect(permissionMemberCreateMock).not.toHaveBeenCalled();
expect(result.details.owner_permission_added).toBeUndefined();
});
});