diff --git a/scripts/test-integration.mjs b/scripts/test-integration.mjs index 289315f..b749518 100644 --- a/scripts/test-integration.mjs +++ b/scripts/test-integration.mjs @@ -86,6 +86,28 @@ ok(tSet.body?.ok === true, 'attachment_set_thumb 回填缩略图', tSet.body); const tGet = await api(A, 'POST', '/api/attachment_thumb', { id: rawUp.b?.data?.item?.id }, T); ok(tGet.body?.data?.data_b64 === Buffer.from('RAWSUMB').toString('base64'), 'attachment_thumb 读回 raw 缩略图', tGet.body); +// ---- admin 用户删除级联 ---- +const invDel = await api(A, 'POST', '/admin/invites/generate', { count: 1 }, T); +const delCode = invDel.body?.data?.codes?.[0]; +const regDel = await api(A, 'POST', '/auth/register', { username: 'deltgt', password: 'password123', invite_code: delCode }); +ok(regDel.status === 200, 'deltgt 注册', regDel.body); +const usersBefore = await api(A, 'GET', '/admin/users', undefined, T); +const delUser = usersBefore.body?.data?.users?.find((u) => u.username === 'deltgt'); +ok(!!delUser, 'deltgt 出现在用户列表', delUser); +const selfUser = usersBefore.body?.data?.users?.find((u) => u.username === 'demo'); +const selfDel = await api(A, 'POST', '/admin/users/delete', { user_id: selfUser.id }, T); +ok(selfDel.status !== 200, '不能删除自己(高危保护)', selfDel.status); +const delResp = await api(A, 'POST', '/admin/users/delete', { user_id: delUser.id }, T); +ok(delResp.status === 200, 'admin_user_delete 断言:删除成功', delResp.body); +const usersAfter = await api(A, 'GET', '/admin/users', undefined, T); +ok(!usersAfter.body?.data?.users?.some((u) => u.username === 'deltgt'), 'deltgt 已从列表移除'); +ok(!fs.existsSync(path.join(dataA, 'users', delUser.id + '.db')), '用户库文件已删除'); +ok(!fs.existsSync(path.join(dataA, 'users', String(delUser.id))), '用户附件目录已删除'); +// 清理测试邀请码,恢复邀请码计数 +const delInv = await api(A, 'GET', '/admin/invites', undefined, T); +const invRow = delInv.body?.data?.invites?.find((i) => i.code === delCode); +if (invRow) { await api(A, 'POST', '/admin/invites/delete', { id: invRow.id }, T); } + // 个人信息:upsert → get 往返 const profileContent = { info: { name: '张三', school: '重庆城市科技学院', major: '网络工程' }, resume: { selfIntro: '热爱安全研究' } }; const pUp = await api(A, 'POST', '/api/profile_upsert', { content: profileContent }, T); diff --git a/sync-server/src/index.mjs b/sync-server/src/index.mjs index 20a505e..a3e0e94 100644 --- a/sync-server/src/index.mjs +++ b/sync-server/src/index.mjs @@ -180,7 +180,7 @@ const server = http.createServer(async (req, res) => { // 用户删除:高危操作,级联清理(图床文件 → 用户库 → 附件目录 → 认证数据) if (req.method === 'POST' && route === '/admin/users/delete') { const body = await readJson(req); - const uid = Number(body.user_id); + const uid = String(body.user_id ?? '').trim(); const target = db.prepare('SELECT id, username, is_admin FROM users WHERE id=?').get(uid); if (!target) return send(404, false, '用户不存在'); if (target.id === auth.user_id) return send(400, false, '不能删除当前登录的账号自己'); @@ -208,6 +208,7 @@ const server = http.createServer(async (req, res) => { // 认证库级联清理 db.exec('BEGIN'); try { + db.prepare('DELETE FROM tokens WHERE user_id=?').run(target.id); db.prepare('DELETE FROM current WHERE user_id=?').run(target.id); db.prepare('DELETE FROM attachments WHERE user_id=?').run(target.id); db.prepare('DELETE FROM devices WHERE user_id=?').run(target.id);