mirror of
https://github.com/ZHANGTIANYAO1/teamspeak-music-bot.git
synced 2026-10-02 04:52:50 +08:00
feat(guest): prune deleted bot from guest scope on removeBot
When a bot is deleted, prune its id from config.guestMode.bots (when an array) and persist, mirroring the existing permissions.pruneBot(id) member-access pruning. Thread CONFIG_PATH into BotManager so removeBot can save the updated config. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
f142c514cd
commit
45414b3baa
3 files changed
+99
-3
No files matched your search
@@ -0,0 +1,87 @@
|
|||||||
|
import { describe, it, expect, afterEach } from "vitest";
|
||||||
|
import { join } from "node:path";
|
||||||
|
import { mkdtempSync, rmSync } from "node:fs";
|
||||||
|
import { tmpdir } from "node:os";
|
||||||
|
import { BotManager } from "./manager.js";
|
||||||
|
import { createDatabase, type BotDatabase } from "../data/database.js";
|
||||||
|
import { createPermissionStore } from "../data/permissions.js";
|
||||||
|
import { getDefaultConfig, loadConfig, saveConfig, type BotConfig } from "../data/config.js";
|
||||||
|
import type { Logger } from "../logger.js";
|
||||||
|
import type { MusicProvider } from "../music/provider.js";
|
||||||
|
import type { AvatarStore } from "../data/avatars.js";
|
||||||
|
|
||||||
|
// removeBot only calls logger.info; provide the full shape it could touch.
|
||||||
|
const stubLogger = {
|
||||||
|
info() {},
|
||||||
|
warn() {},
|
||||||
|
error() {},
|
||||||
|
debug() {},
|
||||||
|
child() {
|
||||||
|
return stubLogger;
|
||||||
|
},
|
||||||
|
} as unknown as Logger;
|
||||||
|
|
||||||
|
describe("BotManager.removeBot — guest scope pruning", () => {
|
||||||
|
const dirs: string[] = [];
|
||||||
|
let db: BotDatabase;
|
||||||
|
|
||||||
|
function makeTmpConfigPath(): string {
|
||||||
|
const dir = mkdtempSync(join(tmpdir(), "tsmusicbot-manager-test-"));
|
||||||
|
dirs.push(dir);
|
||||||
|
return join(dir, "config.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeManager(config: BotConfig, configPath: string): BotManager {
|
||||||
|
db = createDatabase(":memory:");
|
||||||
|
const permissions = createPermissionStore(db.db);
|
||||||
|
saveConfig(configPath, config);
|
||||||
|
return new BotManager(
|
||||||
|
{} as unknown as MusicProvider,
|
||||||
|
{} as unknown as MusicProvider,
|
||||||
|
{} as unknown as MusicProvider,
|
||||||
|
db,
|
||||||
|
config,
|
||||||
|
stubLogger,
|
||||||
|
{} as unknown as AvatarStore,
|
||||||
|
permissions,
|
||||||
|
configPath
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
try {
|
||||||
|
db?.close();
|
||||||
|
} catch {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
|
for (const d of dirs) {
|
||||||
|
rmSync(d, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
dirs.length = 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("prunes a deleted bot from guestMode.bots (array) and persists", async () => {
|
||||||
|
const configPath = makeTmpConfigPath();
|
||||||
|
const config = getDefaultConfig();
|
||||||
|
config.guestMode.bots = ["botA", "botB"];
|
||||||
|
const manager = makeManager(config, configPath);
|
||||||
|
|
||||||
|
await manager.removeBot("botA");
|
||||||
|
|
||||||
|
expect(config.guestMode.bots).toEqual(["botB"]);
|
||||||
|
// Persisted file must also reflect the prune.
|
||||||
|
expect(loadConfig(configPath).guestMode.bots).toEqual(["botB"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('leaves guestMode.bots === "all" unchanged (no crash, no change)', async () => {
|
||||||
|
const configPath = makeTmpConfigPath();
|
||||||
|
const config = getDefaultConfig();
|
||||||
|
config.guestMode.bots = "all";
|
||||||
|
const manager = makeManager(config, configPath);
|
||||||
|
|
||||||
|
await manager.removeBot("botA");
|
||||||
|
|
||||||
|
expect(config.guestMode.bots).toBe("all");
|
||||||
|
expect(loadConfig(configPath).guestMode.bots).toBe("all");
|
||||||
|
});
|
||||||
|
});
|
||||||
+10
-2
@@ -7,7 +7,7 @@ import {
|
|||||||
import type { MusicProvider } from "../music/provider.js";
|
import type { MusicProvider } from "../music/provider.js";
|
||||||
import { YouTubeProvider } from "../music/youtube.js";
|
import { YouTubeProvider } from "../music/youtube.js";
|
||||||
import type { BotDatabase } from "../data/database.js";
|
import type { BotDatabase } from "../data/database.js";
|
||||||
import type { BotConfig } from "../data/config.js";
|
import { saveConfig, type BotConfig } from "../data/config.js";
|
||||||
import type { Logger } from "../logger.js";
|
import type { Logger } from "../logger.js";
|
||||||
|
|
||||||
import type { ServerProtocol } from "../ts-protocol/client.js";
|
import type { ServerProtocol } from "../ts-protocol/client.js";
|
||||||
@@ -79,6 +79,7 @@ export class BotManager extends EventEmitter {
|
|||||||
private logger: Logger;
|
private logger: Logger;
|
||||||
private avatarStore: AvatarStore;
|
private avatarStore: AvatarStore;
|
||||||
private permissions: PermissionStore;
|
private permissions: PermissionStore;
|
||||||
|
private configPath: string;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
neteaseProvider: MusicProvider,
|
neteaseProvider: MusicProvider,
|
||||||
@@ -88,7 +89,8 @@ export class BotManager extends EventEmitter {
|
|||||||
config: BotConfig,
|
config: BotConfig,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
avatarStore: AvatarStore,
|
avatarStore: AvatarStore,
|
||||||
permissions: PermissionStore
|
permissions: PermissionStore,
|
||||||
|
configPath: string
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.neteaseProvider = neteaseProvider;
|
this.neteaseProvider = neteaseProvider;
|
||||||
@@ -100,6 +102,7 @@ export class BotManager extends EventEmitter {
|
|||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
this.avatarStore = avatarStore;
|
this.avatarStore = avatarStore;
|
||||||
this.permissions = permissions;
|
this.permissions = permissions;
|
||||||
|
this.configPath = configPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
async createBot(params: CreateBotParams): Promise<BotInstance> {
|
async createBot(params: CreateBotParams): Promise<BotInstance> {
|
||||||
@@ -160,6 +163,11 @@ export class BotManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
this.database.deleteBotInstance(id);
|
this.database.deleteBotInstance(id);
|
||||||
this.permissions.pruneBot(id);
|
this.permissions.pruneBot(id);
|
||||||
|
// Prune the deleted bot from the guest scope allow-list (mirrors permissions.pruneBot).
|
||||||
|
if (Array.isArray(this.config.guestMode.bots) && this.config.guestMode.bots.includes(id)) {
|
||||||
|
this.config.guestMode.bots = this.config.guestMode.bots.filter((b) => b !== id);
|
||||||
|
saveConfig(this.configPath, this.config);
|
||||||
|
}
|
||||||
this.emit("botInstanceRemoved", id);
|
this.emit("botInstanceRemoved", id);
|
||||||
this.logger.info({ botId: id }, "Bot instance removed");
|
this.logger.info({ botId: id }, "Bot instance removed");
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -74,7 +74,8 @@ async function main() {
|
|||||||
config,
|
config,
|
||||||
logger,
|
logger,
|
||||||
avatarStore,
|
avatarStore,
|
||||||
permissions
|
permissions,
|
||||||
|
CONFIG_PATH
|
||||||
);
|
);
|
||||||
await botManager.loadSavedBots();
|
await botManager.loadSavedBots();
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user