Files
koc-loop/tests/account-tagging.test.mjs

222 lines
6.6 KiB
JavaScript
Raw Permalink Normal View History

import assert from "node:assert/strict";
import test from "node:test";
import {
buildAccountTagText,
generateAccountTagsFromMcp,
parseAccountTags,
} from "../lib/mcp-collection-client.ts";
import { needsResourceEnrichment } from "../lib/resource-import.ts";
function sse(payload, headers = {}) {
return new Response(`event: message\ndata: ${JSON.stringify(payload)}\n\n`, {
status: 200,
headers: {
"content-type": "text/event-stream",
...headers,
},
});
}
function toolEnvelope(payload, isError = false) {
return {
jsonrpc: "2.0",
id: "tool-call",
result: {
isError,
content: [{ type: "text", text: JSON.stringify(payload) }],
},
};
}
function createFakeTaggingMcp(toolResult, isError = false) {
const calls = [];
const fetchImpl = async (url, init) => {
const body = JSON.parse(init.body);
calls.push({ url: String(url), body });
if (body.method === "initialize") {
return sse(
{
jsonrpc: "2.0",
id: body.id,
result: {
protocolVersion: "2025-03-26",
capabilities: {},
serverInfo: { name: "ai-tool-center", version: "test" },
},
},
{ "mcp-session-id": "session-test" },
);
}
if (body.method === "notifications/initialized") {
return new Response("", { status: 202 });
}
if (body.method === "tools/call") {
return sse(toolEnvelope(toolResult, isError));
}
throw new Error(`Unexpected MCP method: ${body.method}`);
};
return { fetchImpl, calls };
}
const config = { endpoint: "https://mcp.test/mcp", key: "test-key" };
const taggingEnv = { AI_TAGGING_TOOL: "generate_tags" };
const input = {
platform: "小红书",
nickname: "小美的日常",
bio: "分享护肤和生活",
gender: "女",
ipLocation: "上海",
followers: 1200,
providerTags: ["美妆"],
recentNoteTitles: ["平价护肤分享"],
};
test("parseAccountTags 解析纯 JSON 数组", () => {
assert.deepEqual(parseAccountTags('["美妆","学生党"]'), ["美妆", "学生党"]);
});
test("parseAccountTags 剥离代码块围栏", () => {
assert.deepEqual(parseAccountTags('```json\n["美食"]\n```'), ["美食"]);
assert.deepEqual(parseAccountTags('```\n["旅行"]\n```'), ["旅行"]);
});
test("parseAccountTags 从夹杂文字中提取数组", () => {
const text = '好的,以下是标签:["旅行","摄影"] 希望有帮助';
assert.deepEqual(parseAccountTags(text), ["旅行", "摄影"]);
});
test("parseAccountTags 清洗与过滤", () => {
assert.deepEqual(parseAccountTags('["#美食","探店#"]'), ["美食", "探店"]);
assert.deepEqual(parseAccountTags('["美妆","美妆","穿搭"]'), [
"美妆",
"穿搭",
]);
assert.deepEqual(
parseAccountTags('["这个标签实在太长了超过十二个字","护肤"]'),
["护肤"],
);
assert.deepEqual(
parseAccountTags('[1, null, "母婴", true]'),
["母婴"],
);
});
test("parseAccountTags 超过 5 个截断", () => {
const text = '["一","二","三","四","五","六"]';
assert.deepEqual(parseAccountTags(text), ["一", "二", "三", "四", "五"]);
});
test("parseAccountTags 无法解析时返回空数组", () => {
assert.deepEqual(parseAccountTags("无法生成标签"), []);
assert.deepEqual(parseAccountTags(""), []);
assert.deepEqual(parseAccountTags('{"tags":"美妆"}'), []);
});
test("buildAccountTagText 包含关键字段", () => {
const text = buildAccountTagText(input);
assert.ok(text.includes("小美的日常"));
assert.ok(text.includes("分享护肤和生活"));
assert.ok(text.includes("粉丝数:1200"));
assert.ok(text.includes("美妆"));
assert.ok(text.includes("平价护肤分享"));
});
test("buildAccountTagText 粉丝数为 0 或空时省略", () => {
assert.ok(!buildAccountTagText({ ...input, followers: 0 }).includes("粉丝数"));
assert.ok(
!buildAccountTagText({ ...input, followers: null }).includes("粉丝数"),
);
});
test("generateAccountTagsFromMcp 工具名为空时不发起请求", async () => {
const fetchImpl = async () => {
throw new Error("should not be called");
};
const tags = await generateAccountTagsFromMcp(
input,
config,
{},
fetchImpl,
);
assert.deepEqual(tags, []);
});
test("generateAccountTagsFromMcp 正常返回标签数组", async () => {
const { fetchImpl, calls } = createFakeTaggingMcp(["美妆", "探店", "好物分享"]);
const tags = await generateAccountTagsFromMcp(
input,
config,
taggingEnv,
fetchImpl,
);
assert.deepEqual(tags, ["美妆", "探店", "好物分享"]);
const toolCall = calls.find((call) => call.body.method === "tools/call");
assert.equal(toolCall.body.params.name, "generate_tags");
const args = toolCall.body.params.arguments;
assert.equal(args.objective, "为KOC账号生成运营标签");
assert.ok(args.text.includes("小美的日常"));
assert.ok(Array.isArray(args.rules) && args.rules.length > 0);
assert.equal(args.output_schema.properties.tags.type, "array");
assert.ok(String(toolCall.url).includes("key=test-key"));
});
test("generateAccountTagsFromMcp 支持对象包裹的返回", async () => {
const { fetchImpl } = createFakeTaggingMcp({ tags: ["母婴", "育儿"] });
const tags = await generateAccountTagsFromMcp(
input,
config,
taggingEnv,
fetchImpl,
);
assert.deepEqual(tags, ["母婴", "育儿"]);
});
test("generateAccountTagsFromMcp 工具报错时返回空数组", async () => {
const { fetchImpl } = createFakeTaggingMcp({ message: "boom" }, true);
const tags = await generateAccountTagsFromMcp(
input,
config,
taggingEnv,
fetchImpl,
);
assert.deepEqual(tags, []);
});
test("generateAccountTagsFromMcp 网络异常时返回空数组", async () => {
const fetchImpl = async () => {
throw new Error("network down");
};
const tags = await generateAccountTagsFromMcp(
input,
config,
taggingEnv,
fetchImpl,
);
assert.deepEqual(tags, []);
});
const completeRow = {
nickname: "小美",
publicAccountId: "xhs-123",
ipLocation: "上海",
followersResolved: true,
gender: "女",
bio: "分享生活",
tags: ["美妆"],
};
test("needsResourceEnrichment 资料齐全且有标签时为 false", () => {
assert.equal(needsResourceEnrichment(completeRow), false);
});
test("needsResourceEnrichment 资料齐全但无标签时为 true", () => {
assert.equal(needsResourceEnrichment({ ...completeRow, tags: [] }), true);
});
test("needsResourceEnrichment 缺资料且有标签时为 true", () => {
assert.equal(
needsResourceEnrichment({ ...completeRow, bio: "" }),
true,
);
});