






















@@ -0,0 +1,131 @@
1+import type * as Lark from "@larksuiteoapi/node-sdk";
2+import { beforeEach, describe, expect, it, vi } from "vitest";
3+import type { OpenClawPluginApi } from "../runtime-api.js";
4+import { createToolFactoryHarness } from "./tool-factory-test-harness.js";
5+6+const createFeishuClientMock = vi.hoisted(() => vi.fn());
7+8+vi.mock("./client.js", () => ({
9+createFeishuClient: createFeishuClientMock,
10+}));
11+12+import { registerFeishuBitableTools } from "./bitable.js";
13+14+type MockRecord = {
15+record_id?: string;
16+fields?: Record<string, unknown>;
17+};
18+19+function createConfig(): OpenClawPluginApi["config"] {
20+return {
21+channels: {
22+feishu: {
23+enabled: true,
24+accounts: {
25+default: {
26+appId: "cli_default",
27+appSecret: "secret_default", // pragma: allowlist secret
28+},
29+},
30+},
31+},
32+} as OpenClawPluginApi["config"];
33+}
34+35+function createBitableClient(records: MockRecord[]) {
36+const batchDelete = vi.fn(async () => ({ code: 0 }));
37+const client = {
38+bitable: {
39+app: {
40+create: vi.fn(async () => ({
41+code: 0,
42+data: {
43+app: {
44+app_token: "app_token",
45+name: "Project Tracker",
46+url: "https://example.feishu.cn/base/app_token",
47+},
48+},
49+})),
50+},
51+appTable: {
52+list: vi.fn(async () => ({
53+code: 0,
54+data: { items: [{ table_id: "tbl_main", name: "Table 1" }] },
55+})),
56+},
57+appTableField: {
58+list: vi.fn(async () => ({ code: 0, data: { items: [] } })),
59+update: vi.fn(async () => ({ code: 0 })),
60+delete: vi.fn(async () => ({ code: 0 })),
61+},
62+appTableRecord: {
63+list: vi.fn(async () => ({ code: 0, data: { items: records } })),
64+ batchDelete,
65+delete: vi.fn(async () => ({ code: 0 })),
66+},
67+},
68+} as unknown as Lark.Client;
69+70+return { batchDelete, client };
71+}
72+73+describe("feishu bitable create app cleanup", () => {
74+beforeEach(() => {
75+createFeishuClientMock.mockReset();
76+});
77+78+it("deletes placeholder rows whose fields contain only default empty values", async () => {
79+const { batchDelete, client } = createBitableClient([
80+{ record_id: "rec_missing_fields" },
81+{ record_id: "rec_empty_fields", fields: {} },
82+{
83+record_id: "rec_empty_defaults",
84+fields: {
85+Name: "",
86+Status: [],
87+Attachments: [],
88+Started: null,
89+EmptyObject: {},
90+},
91+},
92+{
93+record_id: "rec_empty_rich_text",
94+fields: { Notes: [{ type: "text", text: "" }] },
95+},
96+{
97+record_id: "rec_empty_nested",
98+fields: { Notes: { value: "", segments: [{ type: "text", text: "" }] } },
99+},
100+{ record_id: "rec_text", fields: { Name: "Milestone" } },
101+{ record_id: "rec_number", fields: { Estimate: 0 } },
102+{ record_id: "rec_boolean", fields: { Done: false } },
103+{ record_id: "rec_link", fields: { Link: { text: "", link: "https://example.com" } } },
104+{ record_id: "rec_attachment", fields: { Attachments: [{ file_token: "boxcn_token" }] } },
105+{ record_id: "rec_user", fields: { Assignee: [{ id: "ou_1", name: "" }] } },
106+{ record_id: "rec_location", fields: { Location: { name: "", location: "116,39" } } },
107+]);
108+createFeishuClientMock.mockReturnValue(client);
109+110+const { api, resolveTool } = createToolFactoryHarness(createConfig());
111+registerFeishuBitableTools(api);
112+113+const result = await resolveTool("feishu_bitable_create_app").execute("call", {
114+name: "Project Tracker",
115+});
116+117+expect(result.details.cleaned_placeholder_rows).toBe(5);
118+expect(batchDelete).toHaveBeenCalledWith({
119+path: { app_token: "app_token", table_id: "tbl_main" },
120+data: {
121+records: [
122+"rec_missing_fields",
123+"rec_empty_fields",
124+"rec_empty_defaults",
125+"rec_empty_rich_text",
126+"rec_empty_nested",
127+],
128+},
129+});
130+});
131+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。