mirror of
https://github.com/ZHANGTIANYAO1/teamspeak-music-bot.git
synced 2026-10-02 04:52:50 +08:00
175 lines
6.1 KiB
TypeScript
175 lines
6.1 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
|
|
// All axios.create(...) instances in qq.ts (qqMusicuApi / qqSearchApi / qqFavApi
|
|
// and the per-instance api) share this single mock so the search test can
|
|
// inspect the outgoing params/body regardless of which client issued them.
|
|
const { mockGet, mockPost } = vi.hoisted(() => ({ mockGet: vi.fn(), mockPost: vi.fn() }));
|
|
vi.mock("axios", () => ({
|
|
default: { create: () => ({ get: mockGet, post: mockPost }) },
|
|
}));
|
|
|
|
import { mapQqAlbums, mapQqSongs, parseQqTrial, QQMusicProvider } from "./qq.js";
|
|
|
|
describe("QQ adapter", () => {
|
|
it("mapQqSongs maps QQMusicApi-style song entries", () => {
|
|
const out = mapQqSongs([
|
|
{
|
|
mid: "001abc",
|
|
name: "Radar Song",
|
|
singer: [{ name: "Singer A" }, { name: "Singer B" }],
|
|
album: { name: "Album A", mid: "alb001" },
|
|
interval: 243,
|
|
},
|
|
]);
|
|
|
|
expect(out).toEqual([
|
|
{
|
|
id: "001abc",
|
|
name: "Radar Song",
|
|
artist: "Singer A / Singer B",
|
|
album: "Album A",
|
|
duration: 243,
|
|
coverUrl: "https://y.gtimg.cn/music/photo_new/T002R300x300M000alb001.jpg",
|
|
platform: "qq",
|
|
vip: false,
|
|
},
|
|
]);
|
|
});
|
|
|
|
it("mapQqSongs maps pay field to vip flag", () => {
|
|
const out = mapQqSongs([
|
|
{ mid: "v1", name: "VIP playplay", singer: [], album: {}, interval: 100, pay: { payplay: 1, paytrackprice: 0 } },
|
|
{ mid: "v2", name: "VIP trackprice", singer: [], album: {}, interval: 100, pay: { payplay: 0, paytrackprice: 1 } },
|
|
{ mid: "f1", name: "Free", singer: [], album: {}, interval: 100, pay: { payplay: 0, paytrackprice: 0 } },
|
|
{ mid: "f2", name: "No pay field", singer: [], album: {}, interval: 100 },
|
|
]);
|
|
expect(out[0].vip).toBe(true);
|
|
expect(out[1].vip).toBe(true);
|
|
expect(out[2].vip).toBe(false);
|
|
expect(out[3].vip).toBe(false);
|
|
});
|
|
|
|
it("parseQqTrial maps isTryout/tryout to trial seconds", () => {
|
|
// 非试听(VIP/免费)
|
|
expect(parseQqTrial({ isTryout: 0 })).toBeUndefined();
|
|
expect(parseQqTrial({})).toBeUndefined();
|
|
// 试听(秒)
|
|
expect(parseQqTrial({ isTryout: 1, tryBegin: 0, tryEnd: 30 })).toBe(30);
|
|
expect(parseQqTrial({ tryout: true, begin: 0, end: 45 })).toBe(45);
|
|
// 毫秒兜底
|
|
expect(parseQqTrial({ isTryout: 1, tryBegin: 0, tryEnd: 30000 })).toBe(30);
|
|
// 异常
|
|
expect(parseQqTrial({ isTryout: 1, tryEnd: 0 })).toBeUndefined();
|
|
});
|
|
|
|
it("mapQqAlbums maps albumMID-style raw entries", () => {
|
|
const raw = [
|
|
{
|
|
albumMID: "abc",
|
|
albumName: "Aero",
|
|
singerName: "Singer A",
|
|
},
|
|
{
|
|
albumMID: "xyz",
|
|
albumName: "Beta",
|
|
singer: [{ name: "Singer B" }, { name: "Singer C" }],
|
|
},
|
|
];
|
|
const out = mapQqAlbums(raw);
|
|
expect(out).toHaveLength(2);
|
|
expect(out[0]).toMatchObject({
|
|
id: "abc",
|
|
name: "Aero",
|
|
artist: "Singer A",
|
|
platform: "qq",
|
|
});
|
|
expect(out[0].coverUrl).toContain("T002R300x300M000abc.jpg");
|
|
expect(out[1].artist).toBe("Singer B / Singer C");
|
|
expect(out[1].coverUrl).toContain("xyz");
|
|
});
|
|
|
|
it("mapQqAlbums returns [] for empty/null input", () => {
|
|
expect(mapQqAlbums([])).toEqual([]);
|
|
expect(mapQqAlbums(null as any)).toEqual([]);
|
|
expect(mapQqAlbums(undefined as any)).toEqual([]);
|
|
});
|
|
|
|
it("mapQqAlbums falls back to albumPic when no albumMID", () => {
|
|
const raw = [{ albumName: "C", albumPic: "https://x/p.jpg", singerName: "S" }];
|
|
const out = mapQqAlbums(raw);
|
|
expect(out[0].coverUrl).toBe("https://x/p.jpg");
|
|
expect(out[0].id).toBe("");
|
|
});
|
|
});
|
|
|
|
describe("QQMusicProvider.search pagination", () => {
|
|
beforeEach(() => {
|
|
mockGet.mockReset();
|
|
mockPost.mockReset();
|
|
});
|
|
|
|
/** musicu.fcg returns one song → primary path succeeds. */
|
|
function musicuOk() {
|
|
mockGet.mockImplementation(async (url: string) => {
|
|
if (url === "/cgi-bin/musicu.fcg") {
|
|
return {
|
|
data: {
|
|
req_0: { data: { body: { song: { list: [{ mid: "m1", name: "S", singer: [], album: {}, interval: 100 }] } } } },
|
|
req_album: { data: { body: { album: { list: [] } } } },
|
|
req_playlist: { data: { body: { songlist: { list: [] } } } },
|
|
},
|
|
};
|
|
}
|
|
return { data: {} };
|
|
});
|
|
}
|
|
|
|
function musicuReqData() {
|
|
const call = mockGet.mock.calls.find((c: any[]) => c[0] === "/cgi-bin/musicu.fcg");
|
|
expect(call, "expected a musicu.fcg call").toBeTruthy();
|
|
return JSON.parse(call![1].params.data);
|
|
}
|
|
|
|
it("adds page_num (offset/limit+1) and limit-driven num_per_page for songs/albums/playlists", async () => {
|
|
musicuOk();
|
|
const p = new QQMusicProvider("http://x");
|
|
await p.search("hello", 20, 20); // page 2
|
|
|
|
const d = musicuReqData();
|
|
expect(d.req_0.param.page_num).toBe(2);
|
|
expect(d.req_0.param.num_per_page).toBe(20);
|
|
// Albums/playlists: num_per_page must be limit-driven (NOT hardcoded 10).
|
|
expect(d.req_album.param.page_num).toBe(2);
|
|
expect(d.req_album.param.num_per_page).toBe(20);
|
|
expect(d.req_playlist.param.page_num).toBe(2);
|
|
expect(d.req_playlist.param.num_per_page).toBe(20);
|
|
});
|
|
|
|
it("defaults offset to 0 → page_num 1 (backward compatible)", async () => {
|
|
musicuOk();
|
|
const p = new QQMusicProvider("http://x");
|
|
await p.search("hello", 20);
|
|
const d = musicuReqData();
|
|
expect(d.req_0.param.page_num).toBe(1);
|
|
});
|
|
|
|
it("fallback client_search_cp sets p to the page cursor", async () => {
|
|
// musicu returns no songs → primary returns null → fallback runs.
|
|
mockGet.mockImplementation(async (url: string) => {
|
|
if (url === "/cgi-bin/musicu.fcg") {
|
|
return { data: { req_0: { data: { body: { song: { list: [] } } } } } };
|
|
}
|
|
// client_search_cp
|
|
return { data: { data: { song: { list: [] }, album: { list: [] } } } };
|
|
});
|
|
const p = new QQMusicProvider("http://x");
|
|
await p.search("hello", 20, 20); // page 2
|
|
|
|
const songCall = mockGet.mock.calls.find(
|
|
(c: any[]) => c[0] === "/soso/fcgi-bin/client_search_cp" && c[1]?.params?.type === 0
|
|
);
|
|
expect(songCall, "expected a client_search_cp song call").toBeTruthy();
|
|
expect(songCall![1].params.p).toBe(2);
|
|
});
|
|
});
|