Files
谢宇宁andClaude Opus 5.5 f65d10e998 Initial commit
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 23:07:59 +07:00

224 lines
7.5 KiB
JavaScript

// Read-only business smoke checks; only the normal guest-session handshake is a POST.
// Cookies stay in memory and are never printed or written to disk.
import assert from "node:assert/strict";
const base = process.env.PREVIEW_URL || "http://127.0.0.1:21030";
const publicOnly = process.env.SMOKE_PUBLIC_ONLY === "1";
const cookies = new Map();
const cookieHeader = () =>
[...cookies].map(([key, value]) => `${key}=${value}`).join("; ");
async function request(path, body) {
const response = await fetch(`${base}/api/app/${path}`, {
method: body === undefined ? "GET" : "POST",
headers: {
Cookie: cookieHeader(),
...(body === undefined ? {} : { "Content-Type": "application/json" }),
},
body: body === undefined ? undefined : JSON.stringify(body),
});
for (const item of response.headers.getSetCookie()) {
const [pair] = item.split(";");
const index = pair.indexOf("=");
cookies.set(pair.slice(0, index), pair.slice(index + 1));
}
const envelope = await response.json();
assert.equal(response.status, 200, `${path}: HTTP ${response.status}`);
assert.equal(envelope.code, 200, `${path}: ${envelope.msg || envelope.tip}`);
return envelope.data;
}
const home = await request("playlet/home");
const response = await fetch(base);
const html = await response.text();
assert.equal(response.status, 200);
assert.ok(!html.includes("data-must-watch-sort"), "首页筛选项应保持移除");
if (!publicOnly) {
const guest = await request("auth/guest", {});
assert.ok(guest.userId, "游客登录应返回用户 ID");
assert.equal(guest.token, undefined, "token 不应暴露给浏览器脚本");
assert.ok(cookies.has("drama_session"), "应通过 Cookie 保持会话");
console.log("PASS: HttpOnly guest session");
}
console.log("PASS: SSR homepage");
const checks = [
"config/info",
"config/footer/info",
"navigation/header",
"translation/category/list",
"theme/list",
"playlet/tag/options",
"playlet/all/list?sortType=1&pageNum=1&pageSize=4",
"playlet/operate/list?type=2&pageNum=1&pageSize=4",
...[1, 2, 3].map(
(rank) => `playlet/hot/rank/list?rankType=${rank}&pageNum=1&pageSize=4`,
),
"playlet/topic/list?pageNum=1&pageSize=4",
"applist/list?categoryId=0",
"recharge/package/list",
"playlet/hot-like",
...(!publicOnly
? [
"auth/getInfo",
"user/center/data",
"playlet/history/list?pageNum=1&pageSize=4",
"playlet/collect/list?pageNum=1&pageSize=4&watchStatus=0",
"playlet/bought/list?pageNum=1&pageSize=4",
"coin/trans/list?page=1&pageSize=10&type=0",
]
: []),
];
const dataByPath = new Map();
for (let offset = 0; offset < checks.length; offset += 4)
await Promise.all(
checks.slice(offset, offset + 4).map(async (path) => {
const data = await request(path);
assert.ok(data, `${path}: missing data`);
dataByPath.set(path.split("?")[0], data);
console.log(`PASS: ${path.split("?")[0]}`);
}),
);
const pages = [
"/shorts/",
"/shorts/page/2/",
"/rank/hot/",
"/rank/new/",
"/rank/follow/",
"/topics/",
"/apps/",
"/home-road/",
"/navigation/",
"/info/about-us/",
"/info/user-agreement/",
"/info/privacy-policy/",
"/info/help-center/",
"/info/2257/",
"/info/dmca/",
"/info/feedback/",
];
const topic = dataByPath
.get("playlet/topic/list")
?.list?.flatMap((group) => group.list || [])?.[0];
if (topic) {
const detail = await request(
`playlet/topic/detail?id=${topic.id}&pageNum=1&pageSize=4`,
);
assert.equal(detail.id, topic.id);
assert.equal(typeof detail.name, "string");
assert.ok(Array.isArray(detail.list));
pages.push(`/topics/${topic.id}/`);
}
const tag = dataByPath.get("playlet/tag/options")?.list?.[0];
if (tag) pages.push(`/tag/${tag.id}/`);
const theme = dataByPath.get("theme/list")?.list?.[0];
if (theme) pages.push(`/theme/${theme.id}/`);
const block = home.modules?.find((item) => [1, 2].includes(item.moduleType));
if (block) {
const page = await request(
`playlet/home-config/list?id=${block.id}&pageNum=1&pageSize=4`,
);
assert.equal(page.config.id, block.id);
pages.push(`/config/${block.id}/`);
}
const first =
home.homeBannerData?.[0] ||
home.modules?.find((block) => block.list?.length)?.list[0];
if (first) {
const search = await request(
"playlet/search?" +
new URLSearchParams({
keyword: first.playName || first.title,
pageNum: "1",
pageSize: "4",
}),
);
assert.ok(Array.isArray(search.list));
pages.push(
"/search/" + encodeURIComponent(first.playName || first.title) + "/",
);
const suggestions = await request(
"playlet/search/suggest?" +
new URLSearchParams({ keyword: first.playName || first.title }),
);
assert.ok(Array.isArray(suggestions.list));
if (!publicOnly) {
const detail = await request(`playlet/detail?id=${first.id}`);
assert.equal(detail.id, first.id);
const chapters = await request(
`playlet/chapter/list?playletId=${first.id}`,
);
const related = await request(
`playlet/guess-like/list?playletId=${first.id}&pageNum=1&pageSize=6`,
);
assert.ok(Array.isArray(related.list));
pages.push(`/detail/${first.id}/`);
if (chapters.list?.length) {
const chapter = await request(
`playlet/chapter/detail?playletId=${first.id}&chapterId=${chapters.list[0].id}`,
);
assert.equal(chapter.id, chapters.list[0].id);
assert.equal(typeof chapter.canWatch, "boolean");
pages.push(`/play/${first.id}/${chapter.id}/`);
}
console.log("PASS: drama detail and chapter permissions");
}
console.log("PASS: search");
}
if (!publicOnly)
pages.push(
"/member/center/",
"/member/favorites/",
"/member/history/",
"/member/purchased/",
"/member/settings/",
"/info/deposit/",
"/info/deposit/vip/",
);
for (let offset = 0; offset < pages.length; offset += 3)
await Promise.all(
pages.slice(offset, offset + 3).map(async (path) => {
const response = await fetch(base + path, {
headers: { Cookie: cookieHeader() },
redirect: "manual",
}),
html = await response.text();
assert.equal(response.status, 200, path);
assert.equal(response.headers.get("location"), null, path);
assert.ok(!html.includes("内容暂不可用,请稍后重试"), path);
assert.ok(!html.includes('class="api-loading"'), path);
const marker = path.startsWith("/member/settings/")
? "member-settings"
: path.startsWith("/member/")
? "member-center"
: path.startsWith("/info/deposit/")
? "wallet-page"
: path === "/info/feedback/"
? 'id="feedback-form"'
: path === "/home-road/"
? "return-home-directory"
: path.startsWith("/search/")
? "data-search-results"
: "";
if (marker)
assert.ok(
html.includes(marker),
path + " must contain the complete server-rendered page",
);
console.log(`PASS: page ${path}`);
}),
);
const rejected = await fetch(`${base}/api/app/not-allowed`);
assert.equal(rejected.status, 404);
const invalidLogin = await fetch(`${base}/api/app/auth/login`, {
method: "POST",
headers: {
Origin: new URL(base).origin,
"Sec-Fetch-Site": "same-origin",
"Content-Type": "application/json",
},
body: "{}",
});
assert.equal(invalidLogin.status, 400);
assert.equal((await invalidLogin.json()).code, 400);
console.log("PASS: proxy endpoint allowlist and browser login input validation");