mirror of
https://github.com/ZHANGTIANYAO1/teamspeak-music-bot.git
synced 2026-10-03 05:22:49 +08:00
feat(spotify): web OAuth endpoints + thread single shared SpotifyOAuth to web + controllers (Stage 3, Task 6)
Add the /api/spotify {login,callback,status} router behind the SpotifyOAuthLike
seam (DI-tested with supertest, no network). Build ONE process-wide SpotifyOAuth
in index.ts (clientId/redirectUri from config; store via the already-exported
createFileOAuthTokenStore) and thread that same instance into BOTH createWebServer
AND BotManager -> BotInstance -> SpotifyController, so a web login authorizes
playback (C3.1). Reuses the existing file token store (no token-store.ts).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
322335dfc5
commit
8998b9623f
8 files changed
+363
-2
No files matched your search
@@ -9,6 +9,7 @@ import { getDefaultConfig, loadConfig, saveConfig, type BotConfig } from "../dat
|
||||
import type { Logger } from "../logger.js";
|
||||
import type { MusicProvider } from "../music/provider.js";
|
||||
import type { AvatarStore } from "../data/avatars.js";
|
||||
import type { SpotifyOAuth } from "../music/spotify/spotify-oauth.js";
|
||||
|
||||
// removeBot only calls logger.info; provide the full shape it could touch.
|
||||
const stubLogger = {
|
||||
@@ -85,3 +86,68 @@ describe("BotManager.removeBot — guest scope pruning", () => {
|
||||
expect(loadConfig(configPath).guestMode.bots).toBe("all");
|
||||
});
|
||||
});
|
||||
|
||||
// --- Spotify OAuth threading (Task 6, C3.1) --------------------------------
|
||||
// The single process-wide SpotifyOAuth built in index.ts must reach every bot's
|
||||
// SpotifyController: index -> BotManager (trailing positional arg) -> BotInstance
|
||||
// -> controller. createBot() builds a REAL (side-effect-free) SpotifyController,
|
||||
// so we assert the shared instance surfaces via the controller's getOAuth().
|
||||
describe("BotManager — spotifyOAuth threading to bot controllers (C3.1)", () => {
|
||||
const dirs: string[] = [];
|
||||
let db: BotDatabase | undefined;
|
||||
|
||||
afterEach(() => {
|
||||
try {
|
||||
db?.close();
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
db = undefined;
|
||||
for (const d of dirs) {
|
||||
rmSync(d, { recursive: true, force: true });
|
||||
}
|
||||
dirs.length = 0;
|
||||
});
|
||||
|
||||
it("forwards its shared SpotifyOAuth into a created bot's controller", async () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "tsmusicbot-oauth-thread-"));
|
||||
dirs.push(dir);
|
||||
const configPath = join(dir, "config.json");
|
||||
const config = getDefaultConfig();
|
||||
saveConfig(configPath, config);
|
||||
db = createDatabase(":memory:");
|
||||
const permissions = createPermissionStore(db.db);
|
||||
const provider = {} as unknown as MusicProvider;
|
||||
const sentinel = {} as unknown as SpotifyOAuth;
|
||||
|
||||
const manager = new BotManager(
|
||||
provider,
|
||||
provider,
|
||||
provider,
|
||||
db,
|
||||
config,
|
||||
stubLogger,
|
||||
{} as unknown as AvatarStore,
|
||||
permissions,
|
||||
configPath,
|
||||
undefined, // localProvider
|
||||
undefined, // kugouProvider
|
||||
undefined, // spotifyProvider
|
||||
join(dir, "spotify"), // spotifyDataDir
|
||||
sentinel, // spotifyOAuth (the single shared instance)
|
||||
);
|
||||
|
||||
const bot = await manager.createBot({
|
||||
name: "b1",
|
||||
serverAddress: "localhost",
|
||||
serverPort: 9987,
|
||||
nickname: "b1",
|
||||
});
|
||||
|
||||
// Full chain observed: the manager's single shared instance is the exact
|
||||
// one the per-bot controller now owns (getOAuth() returns it unchanged).
|
||||
expect(bot.getSpotifyController().getOAuth()).toBe(sentinel);
|
||||
|
||||
bot.disconnect();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user