mirror of
https://github.com/ZHANGTIANYAO1/teamspeak-music-bot.git
synced 2026-10-01 20:42:50 +08:00
145 lines
5.3 KiB
TypeScript
145 lines
5.3 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
import express from "express";
|
|
import request from "supertest";
|
|
import pino from "pino";
|
|
import type { MusicProvider, SearchResult } from "../../music/provider.js";
|
|
import { getDefaultConfig, type BotConfig } from "../../data/config.js";
|
|
import { createMusicRouter } from "./music.js";
|
|
|
|
const empty: SearchResult = { songs: [], albums: [], playlists: [] };
|
|
|
|
function fakeProvider(platform: MusicProvider["platform"]): MusicProvider {
|
|
return {
|
|
platform,
|
|
search: vi.fn().mockResolvedValue(empty),
|
|
} as unknown as MusicProvider;
|
|
}
|
|
|
|
describe("music router GET /search offset pagination", () => {
|
|
let app: express.Express;
|
|
let netease: MusicProvider;
|
|
|
|
beforeEach(() => {
|
|
netease = fakeProvider("netease");
|
|
const router = createMusicRouter(
|
|
netease,
|
|
fakeProvider("qq"),
|
|
fakeProvider("bilibili"),
|
|
pino({ level: "silent" })
|
|
);
|
|
app = express();
|
|
app.use("/api/music", router);
|
|
});
|
|
|
|
it("parses offset and passes it as the 3rd arg to provider.search", async () => {
|
|
const res = await request(app).get("/api/music/search?q=hello&limit=20&offset=20");
|
|
expect(res.status).toBe(200);
|
|
expect(netease.search).toHaveBeenCalledWith("hello", 20, 20);
|
|
});
|
|
|
|
it("defaults a missing offset to 0", async () => {
|
|
const res = await request(app).get("/api/music/search?q=hello&limit=20");
|
|
expect(res.status).toBe(200);
|
|
expect(netease.search).toHaveBeenCalledWith("hello", 20, 0);
|
|
});
|
|
|
|
it("clamps a negative offset to 0", async () => {
|
|
const res = await request(app).get("/api/music/search?q=hello&limit=20&offset=-5");
|
|
expect(res.status).toBe(200);
|
|
expect(netease.search).toHaveBeenCalledWith("hello", 20, 0);
|
|
});
|
|
});
|
|
|
|
describe("music router provider gating (enabledProviders) + jellyfin endpoints", () => {
|
|
function jellyfinFake(): MusicProvider {
|
|
return {
|
|
platform: "jellyfin",
|
|
search: vi.fn().mockResolvedValue(empty),
|
|
getQuality: vi.fn().mockReturnValue("direct"),
|
|
getLatestAlbums: vi
|
|
.fn()
|
|
.mockResolvedValue([{ id: "a1", name: "Album", platform: "jellyfin" }]),
|
|
getFavoriteSongs: vi.fn().mockResolvedValue([]),
|
|
} as unknown as MusicProvider;
|
|
}
|
|
|
|
function mount(config: BotConfig) {
|
|
const netease = fakeProvider("netease");
|
|
const jellyfin = jellyfinFake();
|
|
const router = createMusicRouter(
|
|
netease,
|
|
fakeProvider("qq"),
|
|
fakeProvider("bilibili"),
|
|
pino({ level: "silent" }),
|
|
undefined,
|
|
config,
|
|
fakeProvider("kugou"),
|
|
fakeProvider("spotify"),
|
|
jellyfin,
|
|
);
|
|
const app = express();
|
|
app.use("/api/music", router);
|
|
return { app, netease, jellyfin };
|
|
}
|
|
|
|
it("routes a platform-less /search to the default platform (jellyfin)", async () => {
|
|
const { app, netease, jellyfin } = mount(getDefaultConfig());
|
|
const res = await request(app).get("/api/music/search?q=hello");
|
|
expect(res.status).toBe(200);
|
|
expect(jellyfin.search).toHaveBeenCalledWith("hello", 20, 0);
|
|
expect(netease.search).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("rejects a disabled platform with 400 without calling its provider", async () => {
|
|
// Default config enables jellyfin only.
|
|
const { app, netease } = mount(getDefaultConfig());
|
|
const res = await request(app).get("/api/music/search?q=hello&platform=netease");
|
|
expect(res.status).toBe(400);
|
|
expect(netease.search).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("allows an explicitly re-enabled legacy platform", async () => {
|
|
const config = getDefaultConfig();
|
|
config.enabledProviders = ["jellyfin", "netease"];
|
|
const { app, netease } = mount(config);
|
|
const res = await request(app).get("/api/music/search?q=hello&platform=netease");
|
|
expect(res.status).toBe(200);
|
|
expect(netease.search).toHaveBeenCalledWith("hello", 20, 0);
|
|
});
|
|
|
|
it("GET /providers reports enabled sources and the default platform", async () => {
|
|
const { app } = mount(getDefaultConfig());
|
|
const res = await request(app).get("/api/music/providers");
|
|
expect(res.status).toBe(200);
|
|
expect(res.body.default).toBe("jellyfin");
|
|
expect(res.body.enabled).toContain("jellyfin");
|
|
expect(res.body.enabled).toContain("local"); // localAudioEnabled defaults on
|
|
expect(res.body.enabled).not.toContain("netease");
|
|
expect(res.body.enabled).not.toContain("spotify"); // spotify.enabled defaults off
|
|
});
|
|
|
|
it("GET /jellyfin/latest-albums returns provider data", async () => {
|
|
const { app, jellyfin } = mount(getDefaultConfig());
|
|
const res = await request(app).get("/api/music/jellyfin/latest-albums?limit=5");
|
|
expect(res.status).toBe(200);
|
|
expect(
|
|
(jellyfin as unknown as { getLatestAlbums: ReturnType<typeof vi.fn> }).getLatestAlbums,
|
|
).toHaveBeenCalledWith(5);
|
|
expect(res.body.albums).toHaveLength(1);
|
|
});
|
|
|
|
it("GET /jellyfin/latest-albums is 400 when jellyfin is disabled", async () => {
|
|
const config = getDefaultConfig();
|
|
config.enabledProviders = ["netease"];
|
|
const { app } = mount(config);
|
|
const res = await request(app).get("/api/music/jellyfin/latest-albums");
|
|
expect(res.status).toBe(400);
|
|
});
|
|
|
|
it("GET /jellyfin/favorites denies unauthenticated/guest access", async () => {
|
|
const { app } = mount(getDefaultConfig());
|
|
const res = await request(app).get("/api/music/jellyfin/favorites");
|
|
expect(res.status).toBe(401);
|
|
});
|
|
});
|