Files
koc-loop/tests/mcp-collection-client.test.mjs
2026-07-30 12:06:41 +08:00

511 lines
13 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 {
collectXhsMetricsFromMcp,
resolveCollectionMcpConfig,
resolveXhsPublicAccountDetails,
resolveXhsPublicAccountId,
resolveXhsAccountProfileFromMcp,
resolveXhsProfileDetailsFromMcp,
xhsNoteIdFromUrl,
} from "../lib/mcp-collection-client.ts";
function sse(payload, options = {}) {
return new Response(
`event: message\ndata: ${JSON.stringify(payload)}\n\n`,
{
status: 200,
...options,
headers: {
"content-type": "text/event-stream",
...(options.headers ?? {}),
},
},
);
}
function toolEnvelope(payload, isError = false) {
return {
jsonrpc: "2.0",
id: "tool-call",
result: {
isError,
content: [
{
type: "text",
text: JSON.stringify(payload),
},
],
},
};
}
function createFakeMcp(toolResults) {
let toolIndex = 0;
const calls = [];
const fetchImpl = async (url, init) => {
const body = JSON.parse(init.body);
calls.push({ url: String(url), body, headers: new Headers(init.headers) });
if (body.method === "initialize") {
return sse(
{
jsonrpc: "2.0",
id: body.id,
result: {
protocolVersion: "2025-03-26",
capabilities: { tools: { listChanged: false } },
serverInfo: { name: "ai-tool-center", version: "test" },
},
},
{ headers: { "mcp-session-id": "session-test" } },
);
}
if (body.method === "notifications/initialized") {
return new Response("", { status: 202 });
}
if (body.method === "tools/call") {
const result = toolResults[toolIndex];
toolIndex += 1;
return sse(result);
}
throw new Error(`Unexpected MCP method: ${body.method}`);
};
return { calls, fetchImpl };
}
test("collects likes, comments and favorites from the verified MCP shape", async () => {
const { calls, fetchImpl } = createFakeMcp([
toolEnvelope({
http_status: 200,
response: {
code: 200,
success: true,
msg: "获取内容详情成功",
data: {
likes: "483",
comments: "41",
collects: "519",
},
},
}),
]);
const result = await collectXhsMetricsFromMcp(
"https://www.xiaohongshu.com/discovery/item/test?xsec_token=valid",
{
endpoint: "https://collector.example/mcp",
key: "test-key",
},
fetchImpl,
);
assert.deepEqual(result, {
likes: 483,
comments: 41,
collects: 519,
});
assert.equal(calls.length, 3);
assert.equal(calls[2].body.params.name, "fetch_content_detail");
assert.equal(calls[2].body.params.arguments.include_comments, false);
assert.equal(calls[2].headers.get("mcp-session-id"), "session-test");
assert.equal(new URL(calls[0].url).searchParams.get("key"), "test-key");
});
test("resolves the real XHS account profile from a submitted note link", async () => {
const { calls, fetchImpl } = createFakeMcp([
toolEnvelope({
response: {
code: 200,
success: true,
data: {
note_detail: {
products: [
{
content_tags: [
{
notes: [
{
ip_location: "重庆",
note_id: "6a671108000000000f004bef",
user: {
nickname: "N我的麻辣烫好了吗",
profile_url:
"https://www.xiaohongshu.com/user/profile/6905cbca0000000037009f49",
red_id: "94329495984",
user_id: "6905cbca0000000037009f49",
},
},
],
},
],
},
],
},
},
},
}),
toolEnvelope({
response: {
code: 200,
success: true,
data: {
fans_count: "734",
},
},
}),
]);
const profile = await resolveXhsAccountProfileFromMcp(
"https://www.xiaohongshu.com/discovery/item/6a671108000000000f004bef",
"回填昵称",
{
endpoint: "https://collector.example/mcp",
key: "test-key",
},
fetchImpl,
);
assert.deepEqual(profile, {
platformUid: "6905cbca0000000037009f49",
nickname: "N我的麻辣烫好了吗",
profileUrl:
"https://www.xiaohongshu.com/user/profile/6905cbca0000000037009f49",
redId: "94329495984",
ipLocation: "重庆",
followers: 734,
});
assert.equal(calls.length, 4);
assert.equal(
calls[2].body.params.name,
"collect_xhs_wen_note_detail",
);
assert.equal(
calls[2].body.params.arguments.note_id,
"6a671108000000000f004bef",
);
assert.equal(calls[3].body.params.name, "parse_xhs_user_summary");
assert.equal(
calls[3].body.params.arguments.url,
"https://www.xiaohongshu.com/user/profile/6905cbca0000000037009f49",
);
assert.equal(
xhsNoteIdFromUrl(
"https://www.xiaohongshu.com/explore/6a671108000000000f004bef",
),
"6a671108000000000f004bef",
);
});
test("resolves followers directly from the supported XHS user summary tool", async () => {
const { calls, fetchImpl } = createFakeMcp([
toolEnvelope({
response: {
code: 200,
success: true,
msg: "用户摘要解析成功",
data: {
user: {
fansCount: 6,
ipLocation: "福建",
nickname: "555 五",
userId: "1020668113",
},
},
},
}),
]);
const details = await resolveXhsProfileDetailsFromMcp(
"https://www.xiaohongshu.com/user/profile/5fb21d32000000000101c23e",
{
endpoint: "https://collector.example/mcp",
key: "test-key",
},
fetchImpl,
);
assert.deepEqual(details, {
followers: 6,
redId: "1020668113",
ipLocation: "福建",
});
assert.equal(calls[2].body.params.name, "parse_xhs_user_summary");
});
test("resolves an xhslink short URL before requesting the author profile", async () => {
const fakeMcp = createFakeMcp([
toolEnvelope({
response: {
code: 200,
success: true,
data: {
note_detail: {
note_id: "6a572da40000000021018bd2",
ip_location: "上海",
user: {
nickname: "短链作者",
user_id: "6905cbca0000000037009f49",
},
},
},
},
}),
toolEnvelope({
response: {
code: 200,
success: true,
data: {
red_id: "1020668113",
followers: "1.2万",
},
},
}),
]);
const fetchImpl = async (url, init) => {
if (String(url) === "http://xhslink.cn/o/AJFyP5dnj7O") {
return new Response(null, {
status: 302,
headers: {
location:
"https://www.xiaohongshu.com/discovery/item/6a572da40000000021018bd2?xsec_token=valid",
},
});
}
return fakeMcp.fetchImpl(url, init);
};
const profile = await resolveXhsAccountProfileFromMcp(
"http://xhslink.cn/o/AJFyP5dnj7O",
"回填昵称",
{
endpoint: "https://collector.example/mcp",
key: "test-key",
},
fetchImpl,
);
assert.equal(profile.nickname, "短链作者");
assert.equal(profile.platformUid, "6905cbca0000000037009f49");
assert.equal(profile.redId, "1020668113");
assert.equal(profile.followers, 12_000);
assert.equal(
profile.profileUrl,
"https://www.xiaohongshu.com/user/profile/6905cbca0000000037009f49",
);
assert.equal(
fakeMcp.calls[2].body.params.arguments.note_id,
"6a572da40000000021018bd2",
);
});
test("uses the public note author when MCP profile lookup fails", async () => {
const fakeMcp = createFakeMcp([
toolEnvelope(
{
response: {
code: 500,
success: false,
msg: "作者详情暂时不可用",
},
},
true,
),
]);
const fetchImpl = async (url, init) => {
if (String(url) === "http://xhslink.cn/o/AJFyP5dnj7O") {
return new Response(
'<script>window.__STATE__={&quot;noteData&quot;:{&quot;desc&quot;:&quot;测试&quot;,&quot;user&quot;:{&quot;userId&quot;:&quot;5fb21d32000000000101c23e&quot;,&quot;nickName&quot;:&quot;555 五&quot;}}}</script>',
{
status: 200,
headers: {
"content-type": "text/html; charset=utf-8",
location:
"https://www.xiaohongshu.com/discovery/item/6a572da40000000021018bd2?xsec_token=valid",
},
},
);
}
if (
String(url) ===
"https://www.xiaohongshu.com/user/profile/5fb21d32000000000101c23e"
) {
return new Response(
'<script>{"redId":"1020668113","interactions":[{"name":"粉丝","count":"6","i18nCount":"6"}]}</script>',
{ status: 200 },
);
}
return fakeMcp.fetchImpl(url, init);
};
const profile = await resolveXhsAccountProfileFromMcp(
"http://xhslink.cn/o/AJFyP5dnj7O",
"回填昵称",
{
endpoint: "https://collector.example/mcp",
key: "test-key",
},
fetchImpl,
);
assert.deepEqual(profile, {
platformUid: "5fb21d32000000000101c23e",
nickname: "555 五",
profileUrl:
"https://www.xiaohongshu.com/user/profile/5fb21d32000000000101c23e",
redId: "1020668113",
ipLocation: "待识别",
followers: 6,
});
assert.equal(
fakeMcp.calls[2].body.params.name,
"collect_xhs_wen_note_detail",
);
});
test("reads the user-visible Xiaohongshu number from a public profile", async () => {
const fetchImpl = async () =>
new Response(
'<div class="redId">小红书号1020668113</div><script>{"redId":"1020668113","interactions":[{"name":"粉丝","count":"10+","i18nCount":"10+"}]}</script>',
{ status: 200 },
);
const accountId = await resolveXhsPublicAccountId(
"https://www.xiaohongshu.com/user/profile/5fb21d32000000000101c23e",
fetchImpl,
);
const details = await resolveXhsPublicAccountDetails(
"https://www.xiaohongshu.com/user/profile/5fb21d32000000000101c23e",
fetchImpl,
);
assert.equal(accountId, "1020668113");
assert.deepEqual(details, {
redId: "1020668113",
followers: 10,
ipLocation: "",
});
});
test("falls back to parse_xhs_note when the primary tool fails", async () => {
const { calls, fetchImpl } = createFakeMcp([
toolEnvelope(
{
response: {
code: 400,
success: false,
msg: "获取内容详情失败",
data: null,
},
},
true,
),
toolEnvelope({
response: {
code: 200,
success: true,
data: {
likes: "1.2万",
comments: 32,
collects: "2,345",
},
},
}),
]);
const result = await collectXhsMetricsFromMcp(
"https://www.xiaohongshu.com/explore/test",
{
endpoint: "https://collector.example/mcp?key=test-key",
},
fetchImpl,
);
assert.deepEqual(result, {
likes: 12_000,
comments: 32,
collects: 2_345,
});
assert.equal(calls[3].body.params.name, "parse_xhs_note");
});
test("requires the MCP key without sending a network request", async () => {
let requested = false;
await assert.rejects(
collectXhsMetricsFromMcp(
"https://www.xiaohongshu.com/explore/test",
resolveCollectionMcpConfig({}),
async () => {
requested = true;
return new Response();
},
),
/MCP采集密钥未配置/,
);
assert.equal(requested, false);
});
test("rebuilds the MCP session after a gateway session miss", async () => {
let initializeCount = 0;
const fetchImpl = async (_url, init) => {
const body = JSON.parse(init.body);
if (body.method === "initialize") {
initializeCount += 1;
return sse(
{
jsonrpc: "2.0",
id: body.id,
result: {
protocolVersion: "2025-03-26",
capabilities: { tools: { listChanged: false } },
serverInfo: { name: "ai-tool-center", version: "test" },
},
},
{
headers: {
"mcp-session-id": `session-${initializeCount}`,
},
},
);
}
if (
body.method === "notifications/initialized" &&
initializeCount === 1
) {
return Response.json(
{
jsonrpc: "2.0",
id: "server-error",
error: { code: -32600, message: "Session not found" },
},
{ status: 404 },
);
}
if (body.method === "notifications/initialized") {
return new Response("", { status: 202 });
}
if (body.method === "tools/call") {
return sse(
toolEnvelope({
response: {
code: 200,
success: true,
data: { likes: 8, comments: 2, collects: 5 },
},
}),
);
}
throw new Error(`Unexpected MCP method: ${body.method}`);
};
const result = await collectXhsMetricsFromMcp(
"https://www.xiaohongshu.com/explore/test",
{
endpoint: "https://collector.example/mcp",
key: "test-key",
},
fetchImpl,
);
assert.deepEqual(result, { likes: 8, comments: 2, collects: 5 });
assert.equal(initializeCount, 2);
});