fix(smoke): 冒烟不再悄悄改掉演示班级密码(先试已知密码,必要时才重置并恢复)

- 现状问题:pnpm verify:desktop 会用管理员接口把被测班级密码改成 smoke123456 且不恢复,
  导致演示实例的「班级码 G101 / 123456」在跑完冒烟后登录失败(本次验证时实际踩到)。
- 现在先按 [冒烟密码, 种子默认 123456] 依次探测:命中就直接用,**完全不改数据**;
  只有当两者都不通时才临时重置,并在脚本收尾(含失败分支)恢复为 123456 并打印日志。
- README 冒烟章节补充说明;演示实例的 G101 密码已恢复为 123456。

验证:verify:desktop 62/62(日志显示「班级账号就绪:G101」即未改动数据)、离线复跑 56/56;
打包便携版 62/62(packaged=true)且跑完后 G101/123456 仍可登录。
This commit is contained in:
LoveChengke committed 2026-09-12 21:06:58 +08:00
1 parent b69f708aa2
commit 80a0efbcfe
2 files changed
+50 -9

No files matched your search

+2
View File
@@ -569,6 +569,8 @@ pnpm dist:win # nsis 安装包 + portable 单文件(需联网下载 NSIS
`ISLAND_SHOTS_DIR`(默认 `.cache/island-shots/`,仓库内留档目录 `docs/screenshots/island/`), `ISLAND_SHOTS_DIR`(默认 `.cache/island-shots/`,仓库内留档目录 `docs/screenshots/island/`),
并对尺寸、宽高比、绘制内容(不透明像素/颜色种类)、紧急态红色像素与外溢红光做断言。 并对尺寸、宽高比、绘制内容(不透明像素/颜色种类)、紧急态红色像素与外溢红光做断言。
冒烟进程使用独立 userData(`ELECTRON_SMOKE_PROFILE`),因此**用户开着客户端也能跑验证**。 冒烟进程使用独立 userData(`ELECTRON_SMOKE_PROFILE`),因此**用户开着客户端也能跑验证**。
冒烟准备班级凭据时**不改任何数据**:先试冒烟密码与种子默认 `123456`,命中就直接用;
都不通才会临时重置该班密码,并在脚本收尾时恢复为 `123456`(日志会打印"已恢复班级密码…冒烟不留副作用")。
## 叫人(老师点名让学生过来) ## 叫人(老师点名让学生过来)
+48 -9
View File
@@ -59,14 +59,24 @@ if (online === undefined) {
/** /**
* 准备一个已知可用的班级账号(学生端已改为班级码 + 班级密码登录): * 准备一个已知可用的班级账号(学生端已改为班级码 + 班级密码登录):
* 用管理员账号登录 → 取第一个班级 → 重置成冒烟专用密码(必要时补一个班级码)。 * 用管理员账号登录 → 取第一个班级 → 先试"已知密码"(冒烟密码 / 种子默认 123456),
* 这样无论被测实例是全新安装还是升级安装,冒烟都能拿到可用凭据。 * 命中就直接用、**不改动任何数据**;都不行才把该班重置成冒烟专用密码,
* 并在脚本收尾时恢复为种子默认密码(避免把演示实例的 123456 悄悄改掉)。
*/ */
async function provisionClassAccount() { async function provisionClassAccount() {
const base = process.env.ELECTRON_SMOKE_API ?? 'http://127.0.0.1:4000/api'; const base = process.env.ELECTRON_SMOKE_API ?? 'http://127.0.0.1:4000/api';
const username = process.env.ELECTRON_SMOKE_ADMIN ?? 'admin'; const username = process.env.ELECTRON_SMOKE_ADMIN ?? 'admin';
const adminPassword = process.env.ELECTRON_SMOKE_ADMIN_PASSWORD ?? 'admin123'; const adminPassword = process.env.ELECTRON_SMOKE_ADMIN_PASSWORD ?? 'admin123';
const password = process.env.ELECTRON_SMOKE_CLASS_PASSWORD ?? 'smoke123456'; const password = process.env.ELECTRON_SMOKE_CLASS_PASSWORD ?? 'smoke123456';
const CANDIDATES = [...new Set([password, process.env.ELECTRON_SMOKE_SEED_PASSWORD ?? '123456'])];
const tryLogin = async (code, candidate) => {
const response = await fetch(`${base}/auth/class-login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code, password: candidate }),
});
return response.ok;
};
try { try {
const loginResponse = await fetch(`${base}/auth/login`, { const loginResponse = await fetch(`${base}/auth/login`, {
method: 'POST', method: 'POST',
@@ -84,16 +94,13 @@ async function provisionClassAccount() {
if (!target) return null; if (!target) return null;
// 冒烟**永不修改班级码**(班级码是给学生用的,不应被测试工具改写): // 冒烟**永不修改班级码**(班级码是给学生用的,不应被测试工具改写):
// 只有"现有班级码 + 目标密码登录失败"时才重置密码,班级码原样保留。 // 只有"现有班级码 + 已知密码全部登录失败"时才重置密码,班级码原样保留。
const code = target.code; const code = target.code;
if (!code) return null; if (!code) return null;
const probe = await fetch(`${base}/auth/class-login`, { for (const candidate of CANDIDATES) {
method: 'POST', if (await tryLogin(code, candidate)) return { code, password: candidate, className: target.name };
headers: { 'Content-Type': 'application/json' }, }
body: JSON.stringify({ code, password }),
});
if (probe.ok) return { code, password, className: target.name };
const patchResponse = await fetch(`${base}/classes/${target.id}/class-account`, { const patchResponse = await fetch(`${base}/classes/${target.id}/class-account`, {
method: 'PATCH', method: 'PATCH',
@@ -103,12 +110,43 @@ async function provisionClassAccount() {
if (!patchResponse.ok) return null; if (!patchResponse.ok) return null;
const patched = (await patchResponse.json())?.data; const patched = (await patchResponse.json())?.data;
// 记下"需要恢复",脚本收尾时改回种子默认密码(已知密码都不通时无法得知原密码)
patchedAccount = { id: target.id, code: patched?.code ?? code, base, authHeaders };
console.log(
`[smoke] 已知密码均不可用,已把班级 ${target.name} 的密码临时重置为 ${password}` +
`(结束时恢复为 ${CANDIDATES[1]})`,
);
return { code: patched?.code ?? code, password, className: target.name }; return { code: patched?.code ?? code, password, className: target.name };
} catch { } catch {
return null; return null;
} }
} }
/** 冒烟临时重置过的班级账号(结束时恢复) */
let patchedAccount = null;
/** 恢复被冒烟临时改过的班级密码(失败只告警,不影响冒烟结论) */
async function restoreClassAccount() {
if (!patchedAccount) return;
try {
const response = await fetch(`${patchedAccount.base}/classes/${patchedAccount.id}/class-account`, {
method: 'PATCH',
headers: patchedAccount.authHeaders,
body: JSON.stringify({
code: patchedAccount.code,
password: process.env.ELECTRON_SMOKE_SEED_PASSWORD ?? '123456',
}),
});
console.log(
response.ok
? '[smoke] 已恢复班级密码为种子默认 123456(冒烟不留副作用)'
: `[smoke] [WARN] 班级密码恢复失败:status=${response.status},请到后台重新设置`,
);
} catch (error) {
console.log(`[smoke] [WARN] 班级密码恢复失败:${error?.message ?? error}`);
}
}
let classCredentials = null; let classCredentials = null;
if (online === '1') { if (online === '1') {
classCredentials = await provisionClassAccount(); classCredentials = await provisionClassAccount();
@@ -140,6 +178,7 @@ function launchSmoke(extraEnv) {
const code = await launchSmoke(); const code = await launchSmoke();
console.log(`\n[smoke] Electron 退出,code=${code}`); console.log(`\n[smoke] Electron 退出,code=${code}`);
await restoreClassAccount();
if (code !== 0) process.exit(code); if (code !== 0) process.exit(code);
/** /**