Files
koc-loop/tests/feishu-client.test.mjs

227 lines
5.9 KiB
JavaScript
Raw Permalink Normal View History

2026-07-30 12:06:41 +08:00
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,
},
]);
});
2026-07-30 12:06:41 +08:00
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,
),
/不是电子表格/,
);
});