diff --git a/src/web/api/bot.test.ts b/src/web/api/bot.test.ts index 9dd17f3..fd5de27 100644 --- a/src/web/api/bot.test.ts +++ b/src/web/api/bot.test.ts @@ -158,6 +158,44 @@ describe("bot router /settings", () => { expect(bot.autoPauseCalls).toEqual([]); } }); + + it("GET /settings includes adminGroups reflecting config", async () => { + config.adminGroups = [6, 8]; + const res = await request(app).get("/api/bot/settings").set("Cookie", cookie); + expect(res.status).toBe(200); + expect(res.body.adminGroups).toEqual([6, 8]); + }); + + it("POST /settings persists a validated adminGroups and GET returns it", async () => { + const res = await request(app) + .post("/api/bot/settings") + .set("Cookie", cookie) + .send({ adminGroups: [6, 8] }); + expect(res.status).toBe(200); + expect(res.body.adminGroups).toEqual([6, 8]); + expect(config.adminGroups).toEqual([6, 8]); + const followUp = await request(app).get("/api/bot/settings").set("Cookie", cookie); + expect(followUp.body.adminGroups).toEqual([6, 8]); + }); + + it("POST /settings filters invalid adminGroups entries (negative, non-integer, non-number)", async () => { + const res = await request(app) + .post("/api/bot/settings") + .set("Cookie", cookie) + .send({ adminGroups: [6, -1, 2.5, "x", 8] }); + expect(res.status).toBe(200); + expect(config.adminGroups).toEqual([6, 8]); + }); + + it("POST /settings ignores a non-array adminGroups (leaves config unchanged)", async () => { + config.adminGroups = [6]; + const res = await request(app) + .post("/api/bot/settings") + .set("Cookie", cookie) + .send({ adminGroups: "6" }); + expect(res.status).toBe(200); + expect(config.adminGroups).toEqual([6]); + }); }); describe("bot router /settings guest-mode gating + persistence", () => { diff --git a/src/web/api/bot.ts b/src/web/api/bot.ts index d6d8338..fa1e0ce 100755 --- a/src/web/api/bot.ts +++ b/src/web/api/bot.ts @@ -36,6 +36,7 @@ export function createBotRouter( res.json({ idleTimeoutMinutes: config.idleTimeoutMinutes ?? 0, autoPauseOnEmpty: config.autoPauseOnEmpty, + adminGroups: config.adminGroups ?? [], guestMode: config.guestMode, }); }); @@ -43,7 +44,7 @@ export function createBotRouter( // POST /api/bot/settings — 保存全局 bot 行为设置 (gated: changing global bot // behavior is a bot.manage operation, consistent with PR #80's permission model) router.post("/settings", requirePermission("bot.manage"), (req, res) => { - const { idleTimeoutMinutes, autoPauseOnEmpty, guestMode } = req.body; + const { idleTimeoutMinutes, autoPauseOnEmpty, guestMode, adminGroups } = req.body; const hasIdle = idleTimeoutMinutes !== undefined; if (hasIdle && (typeof idleTimeoutMinutes !== "number" || idleTimeoutMinutes < 0)) { @@ -74,6 +75,13 @@ export function createBotRouter( } } + if (Array.isArray(adminGroups)) { + config.adminGroups = adminGroups.filter( + (g: unknown): g is number => + typeof g === "number" && Number.isInteger(g) && g >= 0, + ); + } + saveConfig(configPath, config); // Guest-mode changed: tear down / re-scope in-flight guest WS sockets so a @@ -92,6 +100,7 @@ export function createBotRouter( res.json({ idleTimeoutMinutes: config.idleTimeoutMinutes ?? 0, autoPauseOnEmpty: config.autoPauseOnEmpty, + adminGroups: config.adminGroups ?? [], guestMode: config.guestMode, }); });