fix(sites): 观众健康检查误报未登录,健康失败原因细分(HC-1)

观众 2026 版首页没有 <a href=logout>:退出按钮在 userbar 的 data-uploader-actions JSON
里由脚本渲染,旧登录标志 a[href*="logout"] 永远不命中,健康检查报"未登录"而用户数据正常。

- audiences 定义 loginSuccess 改为 #info_block / usercp.php / logout 任一(0.2.1)
- 健康检查判定细分:登录页 / 权限页 / 疑似人机验证 / 找不到登录标志(带选择器),
  原始判定写入 last_health.detail,站点概览技术信息展示
- 首页只是找不到登录标志时,以 24 小时内用户数据解析成功为已登录证据(同 MP)
- 新 SiteError 类 challenge(cf-mitigated: challenge),不算凭据错误;健康状态 challenge
- testkit mock 增加 setHeaderLayout;加观众首页脱敏结构样本的差分测试

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-30 20:38:48 +08:00
1 parent 322ecb2f8c
commit b1d30bd6ee
16 files changed
+433 -27

No files matched your search

@@ -218,9 +218,35 @@ describe.skipIf(!up)("站点中心(mock NexusPHP)", () => {
mock.setLoggedIn(false);
const bad = await json(await h.req(`/sites/${siteId}/health-check`, { method: "POST" }));
expect(bad).toMatchObject({ ok: false, loggedIn: false, healthStatus: "failing" });
expect(bad.detail).toMatchObject({ reason: "login_page", path: "index.php" });
expect(bad.message).toBe("健康检查:首页返回登录页,Cookie 已失效");
mock.setLoggedIn(true);
});
it("HC-1 健康检测:首页不是登录页、只是找不到登录标志时,以近期用户数据解析成功为准", async () => {
mock.setHeaderLayout("bare");
try {
const r = await json(await h.req(`/sites/${siteId}/health-check`, { method: "POST" }));
expect(r).toMatchObject({ ok: true, status: "ok", loggedIn: true, healthStatus: "healthy", message: null });
expect(r.detail).toMatchObject({ reason: "user_data", homepage: "marker_missing" });
expect(r.detail.userDataAt).toBeTruthy();
// 没有近期用户数据:照实报"找不到登录标志(选择器 …)"
await h.db
.updateTable("site_account")
.set({ user_data_at: new Date(Date.now() - 3 * 86_400_000) })
.where("site_id", "=", siteId)
.execute();
const bad = await json(await h.req(`/sites/${siteId}/health-check`, { method: "POST" }));
expect(bad).toMatchObject({ ok: false, status: "auth_expired", loggedIn: false });
expect(bad.message).toContain("首页里找不到登录标志(选择器 #info_block");
expect(bad.detail).toMatchObject({ reason: "marker_missing" });
const site = await json(await h.req(`/sites/${siteId}`));
expect(site.lastHealth.detail).toMatchObject({ reason: "marker_missing" });
} finally {
mock.setHeaderLayout("classic");
}
});
it("删除站点:账号与凭证删除,资源保留并标站点已关闭;同一定义可再登记", async () => {
const rel = await h.db
.insertInto("source_release")