// 部署后冒烟:node scripts/smoke.mjs const [base, user, pass] = process.argv.slice(2); if (!base || !user || !pass) { console.error('用法: node scripts/smoke.mjs '); process.exit(2); } let fail = 0; const ok = (cond, label, extra) => { console.log((cond ? ' ✓ ' : ' ✗ ') + label + (cond ? '' : ' | ' + JSON.stringify(extra).slice(0, 160))); if (!cond) fail++; }; const health = await fetch(base + '/health').then((r) => r.json()).catch((e) => ({ error: String(e) })); ok(health?.data?.status === 'ok', '/health', health); const login = await fetch(base + '/auth/login', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ username: user, password: pass }) }).then((r) => r.json()).catch((e) => ({ error: String(e) })); ok(login?.ok === true, '登录', login); const token = login?.data?.token; if (!token) { console.error('登录失败,后续跳过'); process.exit(1); } const me = await fetch(base + '/auth/me', { headers: { authorization: 'Bearer ' + token } }).then((r) => r.json()); ok(me?.data?.username === user, '/auth/me 用户名', me); const fp = await fetch(base + '/api/footprint_list', { method: 'POST', headers: { 'content-type': 'application/json', authorization: 'Bearer ' + token }, body: JSON.stringify({ query: { page: 1, page_size: 1 } }) }).then((r) => r.json()); ok(fp?.ok === true, '/api/footprint_list(当前账号 ' + (fp?.data?.total ?? '?') + ' 条)', fp?.error); const inv = await fetch(base + '/admin/invites', { headers: { authorization: 'Bearer ' + token } }).then((r) => r.json()).catch(() => null); if (inv) ok(inv?.ok === true, '/admin/invites(管理员)', inv); else console.log(' - 非管理员,跳过 invites 检查'); console.log(fail ? '\n✗ 冒烟未通过' : '\n✓ 冒烟通过'); process.exit(fail ? 1 : 0);