224 lines
7.5 KiB
JavaScript
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");
|