"use strict"; const fs = require("node:fs"); const test = require("node:test"); const assert = require("node:assert/strict"); const Module = require("node:module"); function loadPluginFresh() { const modulePath = require.resolve("./music_server"); delete require.cache[modulePath]; return require("./music_server"); } function loadPluginFromCode(code) { const moduleObj = { exports: {} }; const wrapped = Function( "'use strict'; return function(require,__musicfree_require,module,exports,console,env,URL,process){\n" + code + "\n}", )(); wrapped( require, require, moduleObj, moduleObj.exports, console, undefined, URL, process, ); return moduleObj.exports; } function clearRuntimeEnv() { delete globalThis.env; } function createHttpClientStub(handler) { const calls = []; async function invoke(call) { calls.push(call); return handler(call, calls.length - 1); } return { calls, client: { async get(url, config) { return invoke({ url, params: (config && config.params) || undefined, }); }, async post(url, data) { return invoke({ method: "post", url, data: data || undefined, }); }, }, }; } async function withPatchedModuleLoad(handler, run) { const originalLoad = Module._load; Module._load = function patchedLoad(request, parent, isMain) { return handler(request, parent, isMain, originalLoad); }; try { return await run(); } finally { Module._load = originalLoad; } } test.afterEach(() => { clearRuntimeEnv(); }); test("can require ./music_server", () => { const plugin = loadPluginFresh(); assert.ok(plugin); }); test("plugin.platform is Music_Server", () => { const plugin = loadPluginFresh(); assert.equal(plugin.platform, "Music_Server"); }); test("plugin.supportedSearchType includes music, artist, and sheet", () => { const plugin = loadPluginFresh(); assert.deepEqual(plugin.supportedSearchType, ["music", "artist", "sheet"]); }); test("plugin.userVariables has only baseUrl and accessToken", () => { const plugin = loadPluginFresh(); const keys = plugin.userVariables.map((item) => item.key); assert.deepEqual(keys, ["baseUrl", "accessToken"]); }); test("readConfigValue reads from env.getUserVariables", () => { globalThis.env = { getUserVariables: () => ({ baseUrl: "https://env-getter.example.com/", accessToken: "getter-token", }), userVariables: { baseUrl: "https://env-user.example.com/", accessToken: "user-token", }, }; const plugin = loadPluginFresh(); plugin.__clearTestState(); assert.equal( plugin.readConfigValue("baseUrl"), "https://env-getter.example.com/", ); assert.equal(plugin.readConfigValue("accessToken"), "getter-token"); }); test("readConfigValue falls back to env.userVariables", () => { globalThis.env = { userVariables: { baseUrl: "https://env-user.example.com/", accessToken: "user-token", }, }; const plugin = loadPluginFresh(); plugin.__clearTestState(); assert.equal( plugin.readConfigValue("baseUrl"), "https://env-user.example.com/", ); assert.equal(plugin.readConfigValue("accessToken"), "user-token"); }); test("readConfigValue tolerates throwing env.userVariables getter", () => { globalThis.env = { get userVariables() { throw new Error("broken getter"); }, }; const plugin = loadPluginFresh(); plugin.__clearTestState(); assert.equal(plugin.readConfigValue("baseUrl"), undefined); assert.equal(plugin.readConfigValue("accessToken"), undefined); }); test("readConfigValue uses testConfig before runtime env", () => { globalThis.env = { getUserVariables: () => ({ baseUrl: "https://env-getter.example.com/", accessToken: "getter-token", }), }; const plugin = loadPluginFresh(); plugin.__clearTestState(); plugin.__setConfigForTests({ baseUrl: "https://test-config.example.com/", accessToken: "test-token", }); assert.equal( plugin.readConfigValue("baseUrl"), "https://test-config.example.com/", ); assert.equal(plugin.readConfigValue("accessToken"), "test-token"); }); test("readRuntimeValue tolerates throwing runtimeValues getter", () => { globalThis.env = { get runtimeValues() { throw new Error("broken runtime values"); }, }; const plugin = loadPluginFresh(); plugin.__clearTestState(); assert.equal(plugin.readRuntimeValue("runtimeClientId"), undefined); assert.equal(plugin.readRuntimeValue("runtimeClientLabel"), undefined); }); test("getClient prefers injected test client", async () => { let axiosRequireCount = 0; await withPatchedModuleLoad((request, parent, isMain, originalLoad) => { if (request === "axios") { axiosRequireCount += 1; throw new Error("should not require axios for injected client"); } return originalLoad(request, parent, isMain); }, async () => { const plugin = loadPluginFresh(); plugin.__clearTestState(); const injectedClient = { type: "injected" }; plugin.__setHttpClientForTests(injectedClient); const client = plugin.getClient(); assert.equal(client, injectedClient); assert.equal(axiosRequireCount, 0); }); }); test("default client is cached and rebuilt when config changes", async () => { let axiosCreateCalls = 0; const createdConfigs = []; await withPatchedModuleLoad((request, parent, isMain, originalLoad) => { if (request === "axios") { return { create: (config) => { axiosCreateCalls += 1; createdConfigs.push(config); return { id: axiosCreateCalls, config, }; }, }; } return originalLoad(request, parent, isMain); }, async () => { globalThis.env = { userVariables: { baseUrl: "https://music-server.example.com/", accessToken: "token-a", }, }; const plugin = loadPluginFresh(); plugin.__clearTestState(); const firstClient = plugin.getClient(); const secondClient = plugin.getClient(); assert.equal(firstClient, secondClient); assert.equal(axiosCreateCalls, 1); assert.equal(createdConfigs[0].baseURL, "https://music-server.example.com"); assert.equal(createdConfigs[0].headers.Authorization, "Bearer token-a"); globalThis.env.userVariables.accessToken = "token-b"; const thirdClient = plugin.getClient(); assert.notEqual(thirdClient, firstClient); assert.equal(axiosCreateCalls, 2); assert.equal(createdConfigs[1].headers.Authorization, "Bearer token-b"); }); }); test("default client sends Authorization and X-Music-Client-Id headers", async () => { const plugin = loadPluginFresh(); plugin.__clearTestState(); plugin.__setConfigForTests({ baseUrl: "https://music-server.example.com", accessToken: "token-a", runtimeClientId: "client-a", runtimeClientLabel: "Alice iPhone", }); let createdConfig = null; await withPatchedModuleLoad((request, parent, isMain, originalLoad) => { if (request === "axios") { return { create: (config) => { createdConfig = config; return { get() {}, post() {} }; }, }; } return originalLoad(request, parent, isMain); }, async () => { plugin.getClient(); }); assert.equal(createdConfig.headers.Authorization, "Bearer token-a"); assert.equal(createdConfig.headers["X-Music-Client-Id"], "client-a"); assert.equal(createdConfig.headers["X-Music-Client-Label"], "Alice iPhone"); }); test("default client strips duplicated Bearer prefix in accessToken", async () => { const plugin = loadPluginFresh(); plugin.__clearTestState(); plugin.__setConfigForTests({ baseUrl: "https://music-server.example.com", accessToken: "Bearer token-with-prefix", }); let createdConfig = null; await withPatchedModuleLoad((request, parent, isMain, originalLoad) => { if (request === "axios") { return { create: (config) => { createdConfig = config; return { get() {}, post() {} }; }, }; } return originalLoad(request, parent, isMain); }, async () => { plugin.getClient(); }); assert.equal( createdConfig.headers.Authorization, "Bearer token-with-prefix", ); }); test("default client sanitizes plugin js url to server root baseURL", async () => { const plugin = loadPluginFresh(); plugin.__clearTestState(); plugin.__setConfigForTests({ baseUrl: "http://64.83.43.123:18081/plugins/music_server.js", accessToken: "token-a", }); let createdConfig = null; await withPatchedModuleLoad((request, parent, isMain, originalLoad) => { if (request === "axios") { return { create: (config) => { createdConfig = config; return { get() {}, post() {} }; }, }; } return originalLoad(request, parent, isMain); }, async () => { plugin.getClient(); }); assert.equal(createdConfig.baseURL, "http://64.83.43.123:18081"); }); test("default client falls back to srcUrl-derived baseURL when baseUrl is empty", async () => { const plugin = loadPluginFresh(); plugin.__clearTestState(); plugin.__setConfigForTests({ baseUrl: "", srcUrl: "http://64.83.43.123:18081/plugins/music_server.js", accessToken: "token-a", }); let createdConfig = null; await withPatchedModuleLoad((request, parent, isMain, originalLoad) => { if (request === "axios") { return { create: (config) => { createdConfig = config; return { get() {}, post() {} }; }, }; } return originalLoad(request, parent, isMain); }, async () => { plugin.getClient(); }); assert.equal(createdConfig.baseURL, "http://64.83.43.123:18081"); }); test("default client prefers srcUrl-derived baseURL over stale configured baseUrl", async () => { const plugin = loadPluginFresh(); plugin.__clearTestState(); plugin.__setConfigForTests({ baseUrl: "http://111.228.62.29:18081", srcUrl: "http://64.83.43.123:18081/plugins/music_server.js", accessToken: "token-a", }); let createdConfig = null; await withPatchedModuleLoad((request, parent, isMain, originalLoad) => { if (request === "axios") { return { create: (config) => { createdConfig = config; return { get() {}, post() {} }; }, }; } return originalLoad(request, parent, isMain); }, async () => { plugin.getClient(); }); assert.equal(createdConfig.baseURL, "http://64.83.43.123:18081"); }); test("distributed plugin uses substituted srcUrl as absolute request base", async () => { const pluginSourcePath = require.resolve("./music_server"); const rawCode = fs.readFileSync(pluginSourcePath, "utf8"); const distributedCode = rawCode.replace( /__MUSIC_SERVER_PLUGIN_SRC_URL__/g, "http://64.83.43.123:18081/plugins/music_server.js", ); const plugin = loadPluginFromCode(distributedCode); const originalFetch = globalThis.fetch; const fetchCalls = []; globalThis.fetch = async (url, options) => { fetchCalls.push({ url, options }); return { ok: true, status: 200, text: async () => JSON.stringify({ pinned: [{ id: "all", title: "all" }], data: [], }), }; }; try { await withPatchedModuleLoad((request, parent, isMain, originalLoad) => { if (request === "axios") { throw new Error("axios unavailable"); } return originalLoad(request, parent, isMain); }, async () => { const result = await plugin.getRecommendSheetTags(); assert.equal(result.pinned[0].id, "all"); }); } finally { globalThis.fetch = originalFetch; } assert.equal(fetchCalls.length, 1); assert.equal( fetchCalls[0].url, "http://64.83.43.123:18081/mf/v1/recommend/tags", ); }); test("default client auto-prepends http scheme when baseUrl has no protocol", async () => { const plugin = loadPluginFresh(); plugin.__clearTestState(); plugin.__setConfigForTests({ baseUrl: "64.83.43.123:18081", accessToken: "token-a", }); let createdConfig = null; await withPatchedModuleLoad((request, parent, isMain, originalLoad) => { if (request === "axios") { return { create: (config) => { createdConfig = config; return { get() {}, post() {} }; }, }; } return originalLoad(request, parent, isMain); }, async () => { plugin.getClient(); }); assert.equal(createdConfig.baseURL, "http://64.83.43.123:18081"); }); test("getRecommendSheetTags falls back to fetch when axios is unavailable", async () => { const plugin = loadPluginFresh(); plugin.__clearTestState(); plugin.__setConfigForTests({ baseUrl: "", srcUrl: "http://64.83.43.123:18081/plugins/music_server.js", accessToken: "", }); const originalFetch = globalThis.fetch; const fetchCalls = []; globalThis.fetch = async (url, options) => { fetchCalls.push({ url, options }); return { ok: true, status: 200, text: async () => JSON.stringify({ pinned: [{ id: "all", title: "all" }], data: [], }), }; }; try { await withPatchedModuleLoad((request, parent, isMain, originalLoad) => { if (request === "axios") { throw new Error("axios unavailable"); } return originalLoad(request, parent, isMain); }, async () => { const result = await plugin.getRecommendSheetTags(); assert.equal(result.pinned[0].id, "all"); }); } finally { globalThis.fetch = originalFetch; } assert.equal(fetchCalls.length, 1); assert.equal(fetchCalls[0].url, "http://64.83.43.123:18081/mf/v1/recommend/tags"); }); test("default client generates stable fallback X-Music-Client-Id from accessToken", async () => { const plugin = loadPluginFresh(); plugin.__clearTestState(); plugin.__setConfigForTests({ baseUrl: "http://64.83.43.123:18081", accessToken: "msv1_token_for_fallback_id", }); const createdConfigs = []; await withPatchedModuleLoad((request, parent, isMain, originalLoad) => { if (request === "axios") { return { create: (config) => { createdConfigs.push(config); return { get() {}, post() {} }; }, }; } return originalLoad(request, parent, isMain); }, async () => { const first = plugin.getClient(); const second = plugin.getClient(); assert.equal(first, second); }); assert.equal(createdConfigs.length, 1); const fallbackClientId = createdConfigs[0].headers["X-Music-Client-Id"]; assert.ok(typeof fallbackClientId === "string"); assert.ok(fallbackClientId.startsWith("mf-fallback-")); assert.ok(fallbackClientId.length > "mf-fallback-".length); }); test("axios remains lazy-loaded", async () => { let axiosRequireCount = 0; await withPatchedModuleLoad((request, parent, isMain, originalLoad) => { if (request === "axios") { axiosRequireCount += 1; return { create: (config) => ({ config }), }; } return originalLoad(request, parent, isMain); }, async () => { const plugin = loadPluginFresh(); plugin.__clearTestState(); assert.equal(axiosRequireCount, 0); globalThis.env = { userVariables: { baseUrl: "https://lazy-load.example.com/", accessToken: "lazy-token", }, }; const client = plugin.getClient(); assert.ok(client); assert.equal(axiosRequireCount, 1); }); }); test("search(music) requests /mf/v1/search/songs and maps to musicItem", async () => { const plugin = loadPluginFresh(); plugin.__clearTestState(); const stub = createHttpClientStub((call) => { if (call.url === "/mf/v1/search/songs") { return { data: { page: 1, page_size: 20, total: 100, data: [ { id: "song-1", title: "Moon", artist: "Artist A", album: "Album A", artwork: "cover-a", duration: 245, }, { id: 2, name: "Moon River", artists: [{ name: "Artist B" }, { name: "Artist C" }], album: { name: "Album B", artwork: "cover-b" }, duration: 180000, }, ], }, }; } throw new Error(`Unexpected request: ${call.url}`); }); plugin.__setHttpClientForTests(stub.client); const result = await plugin.search("moon", 1, "music"); assert.deepEqual(stub.calls, [ { url: "/mf/v1/search/songs", params: { q: "moon", page: 1, page_size: 20, }, }, ]); assert.deepEqual(result, { isEnd: false, data: [ { id: "song-1", title: "Moon", artist: "Artist A", album: "Album A", artwork: "cover-a", duration: 245, }, { id: "2", title: "Moon River", artist: "Artist B, Artist C", album: "Album B", artwork: "cover-b", duration: 180, }, ], }); }); test("search(artist) requests /mf/v1/search/artists and maps to artist items", async () => { const plugin = loadPluginFresh(); plugin.__clearTestState(); const stub = createHttpClientStub((call) => { if (call.url === "/mf/v1/search/artists") { return { data: { isEnd: true, data: [ { id: "catalogsync:artist:1", name: "Singer A", avatar: "artist-cover", description: "artist-desc", worksNum: 12, supportedArtistTabs: ["music"], }, ], }, }; } throw new Error(`Unexpected request: ${call.url}`); }); plugin.__setHttpClientForTests(stub.client); const result = await plugin.search("singer", 1, "artist"); assert.deepEqual(stub.calls, [ { url: "/mf/v1/search/artists", params: { q: "singer", page: 1, page_size: 20, }, }, ]); assert.deepEqual(result, { isEnd: true, data: [ { id: "catalogsync:artist:1", name: "Singer A", avatar: "artist-cover", description: "artist-desc", worksNum: 12, platform: "catalogsync", supportedArtistTabs: ["music"], }, ], }); }); test("search(sheet) requests /mf/v1/search/sheets and maps to sheet items", async () => { const plugin = loadPluginFresh(); plugin.__clearTestState(); const stub = createHttpClientStub((call) => { if (call.url === "/mf/v1/search/sheets") { return { data: { isEnd: true, data: [ { id: "catalogsync:playlist:1", title: "Playlist A", coverImg: "sheet-cover", description: "sheet-desc", worksNum: 20, playableSongCount: 18, play_count: 99, }, ], }, }; } throw new Error(`Unexpected request: ${call.url}`); }); plugin.__setHttpClientForTests(stub.client); const result = await plugin.search("playlist", 1, "sheet"); assert.deepEqual(stub.calls, [ { url: "/mf/v1/search/sheets", params: { q: "playlist", page: 1, page_size: 20, }, }, ]); assert.deepEqual(result, { isEnd: true, data: [ { id: "catalogsync:playlist:1", title: "Playlist A", description: "sheet-desc", coverImg: "sheet-cover", worksNum: 20, playableWorksNum: 18, play_count: 99, }, ], }); }); test("recommend tag APIs hit /mf/v1/recommend/tags and /mf/v1/recommend/sheets", async () => { const plugin = loadPluginFresh(); plugin.__clearTestState(); const stub = createHttpClientStub((call) => { if (call.url === "/mf/v1/recommend/tags") { return { data: { pinned: [{ id: "hot", title: "热门" }], data: [ { title: "语种", data: [{ id: "cn", name: "华语" }], }, ], }, }; } if (call.url === "/mf/v1/recommend/sheets") { return { data: { page: 1, page_size: 60, total: 61, data: [ { id: "catalogsync:playlist:18165", name: "夜晚歌单", creator: { nickname: "Editor" }, description: "desc", coverImg: "sheet-cover", trackCount: 88, play_count: 9999, }, ], }, }; } throw new Error(`Unexpected request: ${call.url}`); }); plugin.__setHttpClientForTests(stub.client); const tags = await plugin.getRecommendSheetTags(); const sheets = await plugin.getRecommendSheetsByTag({ id: "cn" }, 1); assert.deepEqual(stub.calls, [ { url: "/mf/v1/recommend/tags", params: undefined }, { url: "/mf/v1/recommend/sheets", params: { tag: "cn", page: 1, page_size: 60, }, }, ]); assert.deepEqual(tags, { pinned: [{ id: "hot", title: "热门" }], data: [ { title: "语种", data: [{ id: "cn", title: "华语" }], }, ], }); assert.deepEqual(sheets, { isEnd: false, data: [ { id: "catalogsync:playlist:18165", title: "夜晚歌单", artist: "Editor", description: "desc", coverImg: "sheet-cover", worksNum: 88, play_count: 9999, }, ], }); }); test('recommend sheets treats the legacy "default" tag as all', async () => { const plugin = loadPluginFresh(); plugin.__clearTestState(); const stub = createHttpClientStub((call) => { if (call.url === "/mf/v1/recommend/sheets") { return { data: { data: [], }, }; } throw new Error(`Unexpected request: ${call.url}`); }); plugin.__setHttpClientForTests(stub.client); const sheets = await plugin.getRecommendSheetsByTag( { id: "", title: "默认" }, 1, ); assert.deepEqual(stub.calls, [ { url: "/mf/v1/recommend/sheets", params: { tag: "all", page: 1, page_size: 60, }, }, ]); assert.deepEqual(sheets, { isEnd: true, data: [], }); }); test("recommend sheets maps playableSongCount to playableWorksNum", async () => { const plugin = loadPluginFresh(); plugin.__clearTestState(); const stub = createHttpClientStub((call) => { if (call.url === "/mf/v1/recommend/sheets") { return { data: { data: [ { id: "catalogsync:playlist:10001", name: "Mapped Sheet", playableSongCount: 23, }, ], }, }; } throw new Error(`Unexpected request: ${call.url}`); }); plugin.__setHttpClientForTests(stub.client); const sheets = await plugin.getRecommendSheetsByTag({ id: "cn" }, 1); assert.deepEqual(sheets, { isEnd: true, data: [ { id: "catalogsync:playlist:10001", title: "Mapped Sheet", playableWorksNum: 23, }, ], }); }); test("recommend sheets keeps zero playable counts", async () => { const plugin = loadPluginFresh(); plugin.__clearTestState(); const stub = createHttpClientStub((call) => { if (call.url === "/mf/v1/recommend/sheets") { return { data: { data: [ { id: "catalogsync:playlist:10002", name: "Zero Count Sheet", playableSongCount: 0, }, { id: "catalogsync:playlist:10003", name: "Zero Works Sheet", playableWorksNum: 0, }, ], }, }; } throw new Error(`Unexpected request: ${call.url}`); }); plugin.__setHttpClientForTests(stub.client); const sheets = await plugin.getRecommendSheetsByTag({ id: "cn" }, 1); assert.deepEqual(sheets, { isEnd: true, data: [ { id: "catalogsync:playlist:10002", title: "Zero Count Sheet", playableWorksNum: 0, }, { id: "catalogsync:playlist:10003", title: "Zero Works Sheet", playableWorksNum: 0, }, ], }); }); test("getMusicSheetInfo page 1 requests detail then tracks and returns sheetItem + musicList", async () => { const plugin = loadPluginFresh(); plugin.__clearTestState(); const stub = createHttpClientStub((call) => { if (call.url === "/mf/v1/playlists/18165") { return { data: { id: "catalogsync:playlist:18165", name: "晚安歌单", creator: { nickname: "DJ" }, description: "sleep songs", coverImg: "playlist-cover", trackCount: 100, play_count: 321, }, }; } if (call.url === "/mf/v1/playlists/18165/tracks") { return { data: { page: 1, page_size: 60, total: 61, data: [ { id: "m1", name: "Moonlight", artists: [{ name: "A" }], album: { name: "Night", coverImg: "album-cover" }, duration: 200000, }, ], }, }; } throw new Error(`Unexpected request: ${call.url}`); }); plugin.__setHttpClientForTests(stub.client); const result = await plugin.getMusicSheetInfo( { id: "catalogsync:playlist:18165" }, 1, ); assert.deepEqual(stub.calls, [ { url: "/mf/v1/playlists/18165", params: undefined }, { url: "/mf/v1/playlists/18165/tracks", params: { page: 1, page_size: 60, }, }, ]); assert.deepEqual(result, { isEnd: false, sheetItem: { id: "catalogsync:playlist:18165", title: "晚安歌单", artist: "DJ", description: "sleep songs", coverImg: "playlist-cover", worksNum: 100, play_count: 321, }, musicList: [ { id: "m1", title: "Moonlight", artist: "A", album: "Night", artwork: "album-cover", duration: 200, }, ], }); }); test("getMusicSheetInfo accepts server musicList payload shape", async () => { const plugin = loadPluginFresh(); plugin.__clearTestState(); const stub = createHttpClientStub((call) => { if (call.url === "/mf/v1/playlists/18165/tracks") { return { data: { isEnd: true, musicList: [ { id: "m1", title: "Moonlight", artist: "A", album: "Night", artwork: "album-cover", duration: 200, }, ], }, }; } throw new Error(`Unexpected request: ${call.url}`); }); plugin.__setHttpClientForTests(stub.client); const result = await plugin.getMusicSheetInfo( { id: "catalogsync:playlist:18165" }, 2, ); assert.deepEqual(stub.calls, [ { url: "/mf/v1/playlists/18165/tracks", params: { page: 2, page_size: 60, }, }, ]); assert.deepEqual(result, { isEnd: true, musicList: [ { id: "m1", title: "Moonlight", artist: "A", album: "Night", artwork: "album-cover", duration: 200, }, ], }); }); test("getTopLists returns server toplist groups", async () => { const plugin = loadPluginFresh(); plugin.__clearTestState(); const stub = createHttpClientStub((call) => { if (call.url === "/mf/v1/toplists") { return { data: { data: [ { title: "官方榜", data: [ { id: "catalogsync:toplist:kuwo_top_16", name: "酷我热歌榜", description: "每周更新", coverImg: "top-cover", }, ], }, ], }, }; } throw new Error(`Unexpected request: ${call.url}`); }); plugin.__setHttpClientForTests(stub.client); const result = await plugin.getTopLists(); assert.deepEqual(stub.calls, [ { url: "/mf/v1/toplists", params: undefined }, ]); assert.deepEqual(result, [ { title: "官方榜", data: [ { id: "catalogsync:toplist:kuwo_top_16", title: "酷我热歌榜", description: "每周更新", coverImg: "top-cover", }, ], }, ]); }); test("getTopListDetail page 1 requests detail then tracks and returns topListItem + musicList", async () => { const plugin = loadPluginFresh(); plugin.__clearTestState(); const stub = createHttpClientStub((call) => { if (call.url === "/mf/v1/toplists/kuwo_top_16") { return { data: { id: "catalogsync:toplist:kuwo_top_16", name: "酷我热歌榜", description: "top hits", coverImg: "top-cover", }, }; } if (call.url === "/mf/v1/toplists/kuwo_top_16/tracks") { return { data: { isEnd: true, data: [ { id: "tm1", title: "Hot Song", artist: "Singer X", album: "Hot Album", artwork: "art-x", duration: 222, }, ], }, }; } throw new Error(`Unexpected request: ${call.url}`); }); plugin.__setHttpClientForTests(stub.client); const result = await plugin.getTopListDetail( { id: "catalogsync:toplist:kuwo_top_16" }, 1, ); assert.deepEqual(stub.calls, [ { url: "/mf/v1/toplists/kuwo_top_16", params: undefined }, { url: "/mf/v1/toplists/kuwo_top_16/tracks", params: { page: 1, page_size: 60, }, }, ]); assert.deepEqual(result, { isEnd: true, topListItem: { id: "catalogsync:toplist:kuwo_top_16", title: "酷我热歌榜", description: "top hits", coverImg: "top-cover", }, musicList: [ { id: "tm1", title: "Hot Song", artist: "Singer X", album: "Hot Album", artwork: "art-x", duration: 222, }, ], }); }); test("getTopListDetail accepts server musicList payload shape", async () => { const plugin = loadPluginFresh(); plugin.__clearTestState(); const stub = createHttpClientStub((call) => { if (call.url === "/mf/v1/toplists/kuwo_top_16/tracks") { return { data: { isEnd: false, musicList: [ { id: "tm1", title: "Hot Song", artist: "Singer X", album: "Hot Album", artwork: "art-x", duration: 222, }, ], }, }; } throw new Error(`Unexpected request: ${call.url}`); }); plugin.__setHttpClientForTests(stub.client); const result = await plugin.getTopListDetail( { id: "catalogsync:toplist:kuwo_top_16" }, 2, ); assert.deepEqual(stub.calls, [ { url: "/mf/v1/toplists/kuwo_top_16/tracks", params: { page: 2, page_size: 60, }, }, ]); assert.deepEqual(result, { isEnd: false, topListItem: { id: "catalogsync:toplist:kuwo_top_16", }, musicList: [ { id: "tm1", title: "Hot Song", artist: "Singer X", album: "Hot Album", artwork: "art-x", duration: 222, }, ], }); }); test("search(non-music) returns empty result without network request", async () => { const plugin = loadPluginFresh(); plugin.__clearTestState(); const stub = createHttpClientStub((call) => { throw new Error(`Unexpected request: ${call.url}`); }); plugin.__setHttpClientForTests(stub.client); const result = await plugin.search("moon", 1, "album"); assert.deepEqual(result, { isEnd: true, data: [], }); assert.equal(stub.calls.length, 0); }); test("search fallback isEnd uses raw list length instead of mapped length", async () => { const plugin = loadPluginFresh(); plugin.__clearTestState(); const fullPageRawList = Array.from({ length: 20 }, (_, index) => ({ id: `song-${index}`, title: `Title-${index}`, artist: "Artist", album: "Album", artwork: "artwork", duration: 200, })); fullPageRawList[7] = {}; const stub = createHttpClientStub((call) => { if (call.url === "/mf/v1/search/songs") { return { data: { page: 1, page_size: 20, data: fullPageRawList, }, }; } throw new Error(`Unexpected request: ${call.url}`); }); plugin.__setHttpClientForTests(stub.client); const result = await plugin.search("moon", 1, "music"); assert.equal(result.data.length, 19); assert.equal(result.isEnd, false); }); test("getMusicSheetInfo page 1 still loads tracks when detail request fails", async () => { const plugin = loadPluginFresh(); plugin.__clearTestState(); const stub = createHttpClientStub((call) => { if (call.url === "/mf/v1/playlists/18165") { throw new Error("detail failed"); } if (call.url === "/mf/v1/playlists/18165/tracks") { return { data: { data: [ { id: "m-1", title: "Track 1", artist: "Singer", album: "Album 1", artwork: "cover-1", duration: 123, }, ], }, }; } throw new Error(`Unexpected request: ${call.url}`); }); plugin.__setHttpClientForTests(stub.client); const result = await plugin.getMusicSheetInfo( { id: "catalogsync:playlist:18165", title: "Fallback Sheet", artist: "Fallback Artist", }, 1, ); assert.deepEqual(stub.calls, [ { url: "/mf/v1/playlists/18165", params: undefined }, { url: "/mf/v1/playlists/18165/tracks", params: { page: 1, page_size: 60, }, }, ]); assert.deepEqual(result.sheetItem, { id: "catalogsync:playlist:18165", title: "Fallback Sheet", artist: "Fallback Artist", }); assert.deepEqual(result.musicList, [ { id: "m-1", title: "Track 1", artist: "Singer", album: "Album 1", artwork: "cover-1", duration: 123, }, ]); }); test("getTopListDetail page 1 still loads tracks when detail request fails", async () => { const plugin = loadPluginFresh(); plugin.__clearTestState(); const stub = createHttpClientStub((call) => { if (call.url === "/mf/v1/toplists/kuwo_top_16") { throw new Error("detail failed"); } if (call.url === "/mf/v1/toplists/kuwo_top_16/tracks") { return { data: { isEnd: true, data: [ { id: "t-1", title: "Top Track", artist: "Top Singer", album: "Top Album", artwork: "top-art", duration: 200, }, ], }, }; } throw new Error(`Unexpected request: ${call.url}`); }); plugin.__setHttpClientForTests(stub.client); const result = await plugin.getTopListDetail( { id: "catalogsync:toplist:kuwo_top_16", title: "Fallback TopList", }, 1, ); assert.deepEqual(stub.calls, [ { url: "/mf/v1/toplists/kuwo_top_16", params: undefined }, { url: "/mf/v1/toplists/kuwo_top_16/tracks", params: { page: 1, page_size: 60, }, }, ]); assert.deepEqual(result.topListItem, { id: "catalogsync:toplist:kuwo_top_16", title: "Fallback TopList", }); assert.deepEqual(result.musicList, [ { id: "t-1", title: "Top Track", artist: "Top Singer", album: "Top Album", artwork: "top-art", duration: 200, }, ]); }); test("getMusicSheetInfo keeps detail metadata when tracks request fails", async () => { const plugin = loadPluginFresh(); plugin.__clearTestState(); const stub = createHttpClientStub((call) => { if (call.url === "/mf/v1/playlists/18165") { return { data: { id: "catalogsync:playlist:18165", name: "Detail Sheet", creator: { nickname: "Detail Author" }, description: "detail description", coverImg: "detail-cover", trackCount: 100, play_count: 998, }, }; } if (call.url === "/mf/v1/playlists/18165/tracks") { throw new Error("tracks failed"); } throw new Error(`Unexpected request: ${call.url}`); }); plugin.__setHttpClientForTests(stub.client); const result = await plugin.getMusicSheetInfo( { id: "catalogsync:playlist:18165", title: "Fallback Sheet", }, 1, ); assert.deepEqual(result, { isEnd: true, sheetItem: { id: "catalogsync:playlist:18165", title: "Detail Sheet", artist: "Detail Author", description: "detail description", coverImg: "detail-cover", worksNum: 100, play_count: 998, }, musicList: [], }); }); test("getTopListDetail keeps detail metadata when tracks request fails", async () => { const plugin = loadPluginFresh(); plugin.__clearTestState(); const stub = createHttpClientStub((call) => { if (call.url === "/mf/v1/toplists/kuwo_top_16") { return { data: { id: "catalogsync:toplist:kuwo_top_16", name: "Detail TopList", description: "detail top description", coverImg: "detail-top-cover", updateFrequency: "weekly", }, }; } if (call.url === "/mf/v1/toplists/kuwo_top_16/tracks") { throw new Error("tracks failed"); } throw new Error(`Unexpected request: ${call.url}`); }); plugin.__setHttpClientForTests(stub.client); const result = await plugin.getTopListDetail( { id: "catalogsync:toplist:kuwo_top_16", title: "Fallback TopList", }, 1, ); assert.deepEqual(result, { isEnd: true, topListItem: { id: "catalogsync:toplist:kuwo_top_16", title: "Detail TopList", description: "detail top description", coverImg: "detail-top-cover", artist: "weekly", }, musicList: [], }); }); test("getMediaSource joins baseUrl when stream url is relative", async () => { const plugin = loadPluginFresh(); plugin.__clearTestState(); plugin.__setConfigForTests({ baseUrl: "https://media.example.com/", accessToken: "", }); const stub = createHttpClientStub((call) => { if (call.method === "post" && call.url === "/mf/v1/media/resolve") { return { data: { stream: { url: "/mf/v1/media/stream/token-1", headers: { Referer: "https://music.example.com", }, }, selected_source: { quality: "super", }, }, }; } throw new Error(`Unexpected request: ${call.method} ${call.url}`); }); plugin.__setHttpClientForTests(stub.client); const result = await plugin.getMediaSource({ id: "song-1" }, "high"); assert.deepEqual(stub.calls, [ { method: "post", url: "/mf/v1/media/resolve", data: { song_id: "song-1", quality: "high", }, }, ]); assert.deepEqual(result, { url: "https://media.example.com/mf/v1/media/stream/token-1", headers: { Referer: "https://music.example.com", }, quality: "super", }); }); test("getPluginStatus requests /auth/v1/token-status", async () => { const plugin = loadPluginFresh(); plugin.__clearTestState(); const stub = createHttpClientStub((call) => { if (call.url === "/auth/v1/token-status") { return { data: { valid: true, status: "active", remainingDays: 89, playableSongCount: 12345, isCurrentClientBound: true, }, }; } throw new Error(`Unexpected request: ${call.url}`); }); plugin.__setHttpClientForTests(stub.client); const result = await plugin.getPluginStatus(); assert.deepEqual(stub.calls, [{ url: "/auth/v1/token-status", params: undefined }]); assert.deepEqual(result, { valid: true, status: "active", remainingDays: 89, playableSongCount: 12345, isCurrentClientBound: true, }); }); test("getMediaSource keeps absolute stream url", async () => { const plugin = loadPluginFresh(); plugin.__clearTestState(); plugin.__setConfigForTests({ baseUrl: "https://media.example.com/", accessToken: "", }); const stub = createHttpClientStub((call) => { if (call.method === "post" && call.url === "/mf/v1/media/resolve") { return { data: { stream: { url: "https://cdn.example.com/audio/song-2.flac", }, }, }; } throw new Error(`Unexpected request: ${call.method} ${call.url}`); }); plugin.__setHttpClientForTests(stub.client); const result = await plugin.getMediaSource({ id: "song-2" }, "standard"); assert.deepEqual(result, { url: "https://cdn.example.com/audio/song-2.flac", headers: {}, quality: "standard", }); }); test("getMediaSource resolves protocol-relative stream url using baseUrl protocol", async () => { const plugin = loadPluginFresh(); plugin.__clearTestState(); plugin.__setConfigForTests({ baseUrl: "https://media.example.com", accessToken: "", }); const stub = createHttpClientStub((call) => { if (call.method === "post" && call.url === "/mf/v1/media/resolve") { return { data: { stream: { url: "//cdn.example.com/audio/song-3.flac", }, }, }; } throw new Error(`Unexpected request: ${call.method} ${call.url}`); }); plugin.__setHttpClientForTests(stub.client); const result = await plugin.getMediaSource({ id: "song-3" }, "high"); assert.deepEqual(result, { url: "https://cdn.example.com/audio/song-3.flac", headers: {}, quality: "high", }); }); test("getMediaSource returns null when resolve request fails", async () => { const plugin = loadPluginFresh(); plugin.__clearTestState(); const stub = createHttpClientStub((call) => { if (call.method === "post" && call.url === "/mf/v1/media/resolve") { throw new Error("resolve failed"); } throw new Error(`Unexpected request: ${call.method} ${call.url}`); }); plugin.__setHttpClientForTests(stub.client); const result = await plugin.getMediaSource({ id: "song-4" }, "standard"); assert.equal(result, null); }); test("getMediaSource returns null when stream url is missing", async () => { const plugin = loadPluginFresh(); plugin.__clearTestState(); const stub = createHttpClientStub((call) => { if (call.method === "post" && call.url === "/mf/v1/media/resolve") { return { data: { stream: { headers: { Cookie: "k=v", }, }, }, }; } throw new Error(`Unexpected request: ${call.method} ${call.url}`); }); plugin.__setHttpClientForTests(stub.client); const result = await plugin.getMediaSource({ id: "song-5" }, "standard"); assert.equal(result, null); }); test("browse methods fail closed on request errors", async () => { const plugin = loadPluginFresh(); plugin.__clearTestState(); const fallbackSheetItem = { id: "catalogsync:playlist:18165", title: "Fallback Sheet", }; const fallbackTopListItem = { id: "catalogsync:toplist:kuwo_top_16", title: "Fallback TopList", }; const stub = createHttpClientStub(() => { throw new Error("network down"); }); plugin.__setHttpClientForTests(stub.client); const searchResult = await plugin.search("moon", 1, "music"); const sheetsResult = await plugin.getRecommendSheetsByTag({ id: "hot" }, 1); const sheetInfoResult = await plugin.getMusicSheetInfo(fallbackSheetItem, 1); const topListsResult = await plugin.getTopLists(); const topListDetailResult = await plugin.getTopListDetail(fallbackTopListItem, 1); assert.deepEqual(searchResult, { isEnd: true, data: [], }); assert.deepEqual(sheetsResult, { isEnd: true, data: [], }); assert.deepEqual(sheetInfoResult, { isEnd: true, sheetItem: fallbackSheetItem, musicList: [], }); assert.deepEqual(topListsResult, []); assert.deepEqual(topListDetailResult, { isEnd: true, topListItem: fallbackTopListItem, musicList: [], }); }); test("getRecommendSheetTags fails closed on request errors", async () => { const plugin = loadPluginFresh(); plugin.__clearTestState(); const stub = createHttpClientStub(() => { throw new Error("network down"); }); plugin.__setHttpClientForTests(stub.client); const result = await plugin.getRecommendSheetTags(); assert.deepEqual(result, { pinned: [], data: [], }); }); test("netease_17000 compatibility shell re-exports music_server", () => { const musicServerPath = require.resolve("./music_server"); const neteasePath = require.resolve("./netease_17000"); delete require.cache[musicServerPath]; delete require.cache[neteasePath]; const musicServer = require("./music_server"); const neteaseCompat = require("./netease_17000"); assert.equal(neteaseCompat, musicServer); });