// 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");