import assert from "node:assert/strict"; import test from "node:test"; import { clearFeishuAccessTokenCacheForTests, readFeishuSource, } from "../lib/feishu-client.ts"; const bindings = { FEISHU_APP_ID: "cli_test", FEISHU_APP_SECRET: "secret_test", }; function apiResponse(data) { return Response.json({ code: 0, msg: "success", data }); } function fakeFeishu(options = {}) { const calls = []; const fetchImpl = async (input, init = {}) => { const url = new URL(String(input)); calls.push({ url, init }); if (url.pathname.endsWith("/tenant_access_token/internal")) { return Response.json({ code: 0, msg: "success", tenant_access_token: "tenant-test", expire: 7200, }); } if (url.pathname.endsWith("/wiki/v2/spaces/get_node")) { return apiResponse({ node: { obj_type: "sheet", obj_token: "spreadsheet-test", }, }); } if (url.pathname.endsWith("/sheets/query")) { return apiResponse({ sheets: options.sheets ?? [ { sheet_id: "sheet-one", title: "内容池", resource_type: "sheet", hidden: false, grid_properties: { row_count: 20, column_count: 10 }, }, ], }); } if (url.pathname.endsWith("/values_batch_get")) { return apiResponse({ valueRanges: [ { values: [ ["作品ID", "标题", "标签", "正文", null, null], [ 7, "一篇测试笔记", "#测试 #KOC", "测试正文", { type: "embed-image", fileToken: "file-token-one", width: 1080, height: 1440, }, [ { type: "embed-image", fileToken: "file-token-two", width: 1080, height: 1440, }, ], ], [8, "", "", "空标题不会导入"], ], }, ], }); } throw new Error(`Unexpected request: ${url.pathname}`); }; return { calls, fetchImpl }; } test("resolves a wiki sheet and imports title, body, tags, and all images", async () => { clearFeishuAccessTokenCacheForTests(); const { calls, fetchImpl } = fakeFeishu(); const source = await readFeishuSource( "https://tenant.feishu.cn/wiki/wiki-test", bindings, fetchImpl, ); assert.equal(source.spreadsheetToken, "spreadsheet-test"); assert.equal(source.sheetId, "sheet-one"); assert.equal(source.sheetName, "内容池"); assert.equal(source.rows.length, 1); assert.equal(source.rows[0].sourceRow, 7); assert.equal(source.rows[0].body, "测试正文\n\n#测试 #KOC"); assert.deepEqual( source.rows[0].images.map((image) => image.fileToken), ["file-token-one", "file-token-two"], ); assert.deepEqual(source.columns, [ "作品ID", "标题", "标签", "正文", "图片1", "图片2", ]); const valuesCall = calls.find((call) => call.url.pathname.endsWith("/values_batch_get"), ); assert.equal(valuesCall.url.searchParams.get("ranges"), "sheet-one!A1:J20"); }); test("imports video attachments from a Feishu video task sheet", async () => { clearFeishuAccessTokenCacheForTests(); const { fetchImpl } = fakeFeishu(); const videoFetch = async (input, init = {}) => { const url = new URL(String(input)); if (url.pathname.endsWith("/values_batch_get")) { return apiResponse({ valueRanges: [ { values: [ ["标题", "内容(标题+正文+tag)", "视频"], [ "一条测试视频", "一条测试视频\n视频正文 #测试", { type: "attachment", fileToken: "video-token-one", text: "demo.mp4", mimeType: "video/mp4", size: 1024, }, ], ], }, ], }); } return fetchImpl(input, init); }; const source = await readFeishuSource( "https://tenant.feishu.cn/wiki/wiki-test", bindings, videoFetch, ); assert.equal(source.rows.length, 1); assert.equal(source.rows[0].title, "一条测试视频"); assert.equal(source.rows[0].body, "一条测试视频\n视频正文 #测试"); assert.deepEqual(source.rows[0].videos, [ { index: 1, fileToken: "video-token-one", name: "demo.mp4", mimeType: "video/mp4", size: 1024, }, ]); }); test("requires an exact sheet link when a workbook has multiple visible sheets", async () => { clearFeishuAccessTokenCacheForTests(); const { fetchImpl } = fakeFeishu({ sheets: [ { sheet_id: "one", title: "内容A", resource_type: "sheet", hidden: false, }, { sheet_id: "two", title: "内容B", resource_type: "sheet", hidden: false, }, ], }); await assert.rejects( readFeishuSource( "https://tenant.feishu.cn/wiki/wiki-test", bindings, fetchImpl, ), /包含多个工作表/, ); }); test("rejects a wiki node that is not a spreadsheet", async () => { clearFeishuAccessTokenCacheForTests(); const fetchImpl = async (input) => { const url = new URL(String(input)); if (url.pathname.endsWith("/tenant_access_token/internal")) { return Response.json({ code: 0, msg: "success", tenant_access_token: "tenant-test", expire: 7200, }); } return apiResponse({ node: { obj_type: "docx", obj_token: "document-test" }, }); }; await assert.rejects( readFeishuSource( "https://tenant.feishu.cn/wiki/wiki-test", bindings, fetchImpl, ), /不是电子表格/, ); });