Files
koc-loop/tests/feishu-client.test.mjs
2026-08-15 03:53:09 +08:00

227 lines
5.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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,
),
/不是电子表格/,
);
});