Skip to content

Commit 45cc17b

Browse files
l-s-cocto-loop-agent
andcommitted
test(mcp): add application integration evidence for LSC-24
Co-authored-by: octo-loop-agent <loop@deepminer.com.cn>
1 parent 941eaa3 commit 45cc17b

3 files changed

Lines changed: 223 additions & 1 deletion

File tree

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { http, HttpResponse } from "msw";
2+
3+
const API_BASE = "/market/api/v1";
4+
const REDACTED_CREATOR = "[redacted-admin]";
5+
6+
function enabled(): boolean {
7+
try {
8+
return sessionStorage.getItem("__e2e_scenario") === "mcp-official";
9+
} catch {
10+
return false;
11+
}
12+
}
13+
14+
const officialListItem = {
15+
mcp_id: "official-search",
16+
name: "Official Search MCP",
17+
slogan: "Platform-maintained web and news search.",
18+
category: "search",
19+
icon: "🔎",
20+
tags: ["search", "official"],
21+
tool_count: 6,
22+
visibility: "system",
23+
source: "system",
24+
creator_name: REDACTED_CREATOR,
25+
created_by_type: "human",
26+
transport: "streamable-http",
27+
match_reasons: [`creator:${REDACTED_CREATOR}`, "tool:web_search"],
28+
updated_at: "2026-07-24T08:00:00Z",
29+
};
30+
31+
const normalListItem = {
32+
...officialListItem,
33+
mcp_id: "community-search",
34+
name: "Community Search MCP",
35+
slogan: "Community-maintained search integration.",
36+
tags: ["search", "community"],
37+
visibility: "public",
38+
source: "space",
39+
creator_name: "Alice",
40+
match_reasons: ["creator:Alice", "tool:web_search"],
41+
};
42+
43+
const detailFor = (item: typeof officialListItem) => ({
44+
...item,
45+
quick_start: {
46+
transport: "streamable-http",
47+
server_name: item.name,
48+
url: "https://example.test/mcp",
49+
},
50+
tools: [{ name: "web_search", description: "Search the web." }],
51+
usage_examples: ["Search for the latest platform documentation."],
52+
faqs: [],
53+
notes: [],
54+
created_at: "2026-07-20T08:00:00Z",
55+
});
56+
57+
export const mcpOfficialHandlers = [
58+
http.get("*/user/devices/:deviceId", () => {
59+
if (!enabled()) return undefined;
60+
return HttpResponse.json({});
61+
}),
62+
http.get(`*${API_BASE}/mcps`, () => {
63+
if (!enabled()) return undefined;
64+
return HttpResponse.json({
65+
data: [officialListItem, normalListItem],
66+
pagination: { total: 2, page: 1, page_size: 20 },
67+
});
68+
}),
69+
http.get(`*${API_BASE}/mcp_categories`, () => {
70+
if (!enabled()) return undefined;
71+
return HttpResponse.json({ data: [{ key: "search", count: 2 }] });
72+
}),
73+
http.get(`*${API_BASE}/mcps/:id`, ({ params }) => {
74+
if (!enabled()) return undefined;
75+
const item =
76+
params.id === officialListItem.mcp_id ? officialListItem : normalListItem;
77+
return HttpResponse.json({ data: detailFor(item) });
78+
}),
79+
];
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/* eslint-disable no-undef -- e2e code runs in Node */
2+
// @caseId C37-mcp-official-publisher
3+
4+
import { test, expect } from "../fixtures-authed";
5+
import type { Page, TestInfo } from "@playwright/test";
6+
7+
const API_BASE = "/market/api/v1";
8+
const REDACTED_CREATOR = "[redacted-admin]";
9+
10+
async function screenshot(page: Page, testInfo: TestInfo, name: string) {
11+
const path = testInfo.outputPath(name);
12+
await page.screenshot({ path, fullPage: true });
13+
await testInfo.attach(name, { path, contentType: "image/png" });
14+
}
15+
16+
test("@C37 @mcp @integration official publisher renders from application API fixture", async ({
17+
authedPage,
18+
}, testInfo) => {
19+
const consoleErrors: string[] = [];
20+
const networkFailures: string[] = [];
21+
const requests: Array<{ method: string; url: string }> = [];
22+
const responses: Array<{ url: string; status: number; visibility?: string }> =
23+
[];
24+
authedPage.on("console", (message) => {
25+
if (message.type() === "error") consoleErrors.push(message.text());
26+
});
27+
authedPage.on("requestfailed", (request) => {
28+
if (request.url().includes(API_BASE)) {
29+
networkFailures.push(
30+
`${request.method()} ${request.url()} ${
31+
request.failure()?.errorText ?? ""
32+
}`
33+
);
34+
}
35+
});
36+
authedPage.on("request", (request) => {
37+
if (request.url().includes(API_BASE)) {
38+
requests.push({ method: request.method(), url: request.url() });
39+
}
40+
});
41+
authedPage.on("response", async (response) => {
42+
if (!response.url().includes(API_BASE)) return;
43+
const body = await response.json().catch(() => null);
44+
const visibility = response.url().includes("/mcps/official-search")
45+
? body?.data?.visibility
46+
: body?.data?.find?.(
47+
(item: { visibility?: string }) => item.visibility === "system"
48+
)?.visibility;
49+
responses.push({
50+
url: response.url(),
51+
status: response.status(),
52+
visibility,
53+
});
54+
});
55+
56+
await authedPage.addInitScript(() => {
57+
sessionStorage.setItem("__e2e_scenario", "mcp-official");
58+
});
59+
await authedPage.goto("/mcp-market/mcp?sid=e2etest");
60+
61+
const officialCard = authedPage.locator(".wk-mcp-card", {
62+
hasText: "Official Search MCP",
63+
});
64+
const normalCard = authedPage.locator(".wk-mcp-card", {
65+
hasText: "Community Search MCP",
66+
});
67+
await expect(officialCard).toBeVisible();
68+
await expect(normalCard).toBeVisible();
69+
await expect(officialCard).toContainText("官方发布");
70+
await expect(officialCard).not.toContainText(REDACTED_CREATOR);
71+
await expect(normalCard).toContainText("Alice");
72+
await expect(officialCard).toHaveClass(/wk-mcp-card--official/);
73+
await expect(normalCard).not.toHaveClass(/wk-mcp-card--official/);
74+
await screenshot(authedPage, testInfo, "mcp-list-light-full.png");
75+
76+
await officialCard.click();
77+
const detailModal = authedPage.getByRole("dialog");
78+
await expect(detailModal).toBeVisible();
79+
await expect(detailModal).toContainText("Official Search MCP");
80+
await expect(detailModal).toContainText("官方发布");
81+
await expect(detailModal).not.toContainText(REDACTED_CREATOR);
82+
await screenshot(authedPage, testInfo, "mcp-detail-light-full.png");
83+
84+
await authedPage
85+
.getByRole("button", { name: "关闭" })
86+
.click()
87+
.catch(async () => {
88+
await authedPage.keyboard.press("Escape");
89+
});
90+
await expect(detailModal).not.toBeVisible();
91+
92+
await normalCard.click();
93+
await expect(detailModal).toBeVisible();
94+
await expect(detailModal).toContainText("Community Search MCP");
95+
await expect(detailModal).toContainText("Alice");
96+
await expect(detailModal).not.toContainText("官方发布");
97+
await screenshot(authedPage, testInfo, "mcp-normal-detail-light-full.png");
98+
await authedPage.getByRole("button", { name: "关闭" }).click();
99+
await expect(detailModal).not.toBeVisible();
100+
101+
await authedPage.evaluate(() =>
102+
document.body.setAttribute("theme-mode", "dark")
103+
);
104+
await screenshot(authedPage, testInfo, "mcp-list-dark-full.png");
105+
106+
await authedPage.setViewportSize({ width: 390, height: 844 });
107+
await expect(officialCard).toBeVisible();
108+
await expect(normalCard).toBeVisible();
109+
await screenshot(authedPage, testInfo, "mcp-list-mobile-dark-full.png");
110+
111+
expect(requests.some(({ url }) => url.includes(`${API_BASE}/mcps?`))).toBe(
112+
true
113+
);
114+
expect(
115+
requests.some(({ url }) => url.endsWith(`${API_BASE}/mcps/official-search`))
116+
).toBe(true);
117+
expect(responses.some(({ visibility }) => visibility === "system")).toBe(
118+
true
119+
);
120+
expect(consoleErrors).toEqual([]);
121+
expect(networkFailures).toEqual([]);
122+
123+
await testInfo.attach("mcp-api-evidence.json", {
124+
body: Buffer.from(
125+
JSON.stringify(
126+
{
127+
fixtureType: "application-level API fixture",
128+
sensitiveFields: "creator_name redacted",
129+
requests,
130+
responses,
131+
},
132+
null,
133+
2
134+
)
135+
),
136+
contentType: "application/json",
137+
});
138+
});

apps/web/src/mocks/handlers.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,10 @@
44
// 本文件 re-export 供 apps/web/src/mocks/browser.ts 消费.
55
import { loopEmptyHandlers } from "../../e2e-kit/msw-handlers/loop-empty";
66
import { chatBaselineHandlers } from "../../e2e-kit/msw-handlers/chat-baseline";
7+
import { mcpOfficialHandlers } from "../../e2e-kit/msw-handlers/mcp-official";
78

8-
export const handlers = [...loopEmptyHandlers, ...chatBaselineHandlers];
9+
export const handlers = [
10+
...mcpOfficialHandlers,
11+
...loopEmptyHandlers,
12+
...chatBaselineHandlers,
13+
];

0 commit comments

Comments
 (0)