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, ); });