diff --git a/src/widgets/dockhand/proxy.test.js b/src/widgets/dockhand/proxy.test.js new file mode 100644 index 000000000..989e14e22 --- /dev/null +++ b/src/widgets/dockhand/proxy.test.js @@ -0,0 +1,82 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import createMockRes from "test-utils/create-mock-res"; + +const { httpProxy, getServiceWidget, logger } = vi.hoisted(() => ({ + httpProxy: vi.fn(), + getServiceWidget: vi.fn(), + logger: { + debug: vi.fn(), + error: vi.fn(), + }, +})); + +vi.mock("utils/logger", () => ({ + default: () => logger, +})); +vi.mock("utils/config/service-helpers", () => ({ + default: getServiceWidget, +})); +vi.mock("utils/proxy/http", () => ({ + httpProxy, +})); +vi.mock("widgets/widgets", () => ({ + default: { + dockhand: { + api: "{url}/{endpoint}", + }, + }, +})); + +import dockhandProxyHandler from "./proxy"; + +describe("widgets/dockhand/proxy", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("retries after a 401 by logging in once", async () => { + getServiceWidget.mockResolvedValue({ + type: "dockhand", + url: "http://dockhand/", + username: "u", + password: "p", + }); + + httpProxy + .mockResolvedValueOnce([401, "application/json", Buffer.from("nope")]) + .mockResolvedValueOnce([200, "application/json", Buffer.from("ok")]) // login + .mockResolvedValueOnce([200, "application/json", Buffer.from("data")]); // retry + + const req = { method: "GET", query: { group: "g", service: "svc", endpoint: "api/v1/status", index: "0" } }; + const res = createMockRes(); + + await dockhandProxyHandler(req, res); + + expect(httpProxy).toHaveBeenCalledTimes(3); + expect(httpProxy.mock.calls[1][0]).toBe("http://dockhand/api/auth/login"); + expect(res.statusCode).toBe(200); + expect(res.body).toEqual(Buffer.from("data")); + }); + + it("returns a sanitized error response for HTTP errors", async () => { + getServiceWidget.mockResolvedValue({ + type: "dockhand", + url: "http://dockhand", + }); + + httpProxy.mockResolvedValueOnce([500, "application/json", Buffer.from("boom")]); + + const req = { + method: "GET", + query: { group: "g", service: "svc", endpoint: "api/v1/status?token=abc", index: "0" }, + }; + const res = createMockRes(); + + await dockhandProxyHandler(req, res); + + expect(res.statusCode).toBe(500); + expect(res.body.error.message).toBe("HTTP Error"); + expect(res.body.error.url).toContain("token=***"); + }); +}); diff --git a/src/widgets/homebox/proxy.test.js b/src/widgets/homebox/proxy.test.js new file mode 100644 index 000000000..ccc4a52ba --- /dev/null +++ b/src/widgets/homebox/proxy.test.js @@ -0,0 +1,73 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import createMockRes from "test-utils/create-mock-res"; + +const { httpProxy, getServiceWidget, cache, logger } = vi.hoisted(() => { + const store = new Map(); + + return { + httpProxy: vi.fn(), + getServiceWidget: vi.fn(), + cache: { + get: vi.fn((k) => store.get(k)), + put: vi.fn((k, v) => store.set(k, v)), + del: vi.fn((k) => store.delete(k)), + _reset: () => store.clear(), + }, + logger: { + debug: vi.fn(), + error: vi.fn(), + }, + }; +}); + +vi.mock("utils/logger", () => ({ + default: () => logger, +})); +vi.mock("utils/config/service-helpers", () => ({ + default: getServiceWidget, +})); +vi.mock("utils/proxy/http", () => ({ + httpProxy, +})); +vi.mock("memory-cache", () => ({ + default: cache, + ...cache, +})); + +import homeboxProxyHandler from "./proxy"; + +describe("widgets/homebox/proxy", () => { + beforeEach(() => { + vi.clearAllMocks(); + cache._reset(); + }); + + it("logs in and returns group statistics + currency", async () => { + getServiceWidget.mockResolvedValue({ + url: "http://homebox", + username: "u", + password: "p", + }); + + httpProxy + .mockResolvedValueOnce([ + 200, + "application/json", + Buffer.from(JSON.stringify({ token: "tok", expiresAt: new Date(Date.now() + 60_000).toISOString() })), + ]) + .mockResolvedValueOnce([200, "application/json", Buffer.from(JSON.stringify({ totalItems: 1, totalUsers: 2 }))]) + .mockResolvedValueOnce([200, "application/json", Buffer.from(JSON.stringify({ currency: "USD" }))]); + + const req = { query: { group: "g", service: "svc", index: "0" } }; + const res = createMockRes(); + + await homeboxProxyHandler(req, res); + + expect(httpProxy).toHaveBeenCalledTimes(3); + expect(httpProxy.mock.calls[0][0]).toBe("http://homebox/api/v1/users/login"); + expect(res.statusCode).toBe(200); + expect(res.body.currencyCode).toBe("USD"); + expect(res.body.users).toBe(2); + }); +}); diff --git a/src/widgets/homebridge/proxy.test.js b/src/widgets/homebridge/proxy.test.js new file mode 100644 index 000000000..34aa05a04 --- /dev/null +++ b/src/widgets/homebridge/proxy.test.js @@ -0,0 +1,96 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import createMockRes from "test-utils/create-mock-res"; + +const { httpProxy, getServiceWidget, cache, logger } = vi.hoisted(() => { + const store = new Map(); + + return { + httpProxy: vi.fn(), + getServiceWidget: vi.fn(), + cache: { + get: vi.fn((k) => store.get(k)), + put: vi.fn((k, v) => store.set(k, v)), + del: vi.fn((k) => store.delete(k)), + _reset: () => store.clear(), + }, + logger: { + debug: vi.fn(), + error: vi.fn(), + }, + }; +}); + +vi.mock("utils/logger", () => ({ + default: () => logger, +})); +vi.mock("utils/config/service-helpers", () => ({ + default: getServiceWidget, +})); +vi.mock("utils/proxy/http", () => ({ + httpProxy, +})); +vi.mock("memory-cache", () => ({ + default: cache, + ...cache, +})); +vi.mock("widgets/widgets", () => ({ + default: { + homebridge: { + api: "{url}/{endpoint}", + }, + }, +})); + +import homebridgeProxyHandler from "./proxy"; + +describe("widgets/homebridge/proxy", () => { + beforeEach(() => { + vi.clearAllMocks(); + cache._reset(); + }); + + it("logs in and aggregates status, versions, plugin updates, and child bridge counts", async () => { + getServiceWidget.mockResolvedValue({ type: "homebridge", url: "http://hb", username: "u", password: "p" }); + + httpProxy + // login + .mockResolvedValueOnce([ + 200, + "application/json", + Buffer.from(JSON.stringify({ access_token: "tok", expires_in: 3600 })), + {}, + ]) + // status + .mockResolvedValueOnce([200, "application/json", Buffer.from(JSON.stringify({ status: "ok" })), {}]) + // version + .mockResolvedValueOnce([200, "application/json", Buffer.from(JSON.stringify({ updateAvailable: true })), {}]) + // child bridges + .mockResolvedValueOnce([ + 200, + "application/json", + Buffer.from(JSON.stringify([{ status: "ok" }, { status: "down" }])), + {}, + ]) + // plugins + .mockResolvedValueOnce([ + 200, + "application/json", + Buffer.from(JSON.stringify([{ updateAvailable: true }, { updateAvailable: false }])), + {}, + ]); + + const req = { query: { group: "g", service: "svc", index: "0" } }; + const res = createMockRes(); + + await homebridgeProxyHandler(req, res); + + expect(res.statusCode).toBe(200); + expect(res.body).toEqual({ + status: "ok", + updateAvailable: true, + plugins: { updatesAvailable: 1 }, + childBridges: { running: 1, total: 2 }, + }); + }); +}); diff --git a/src/widgets/jackett/proxy.test.js b/src/widgets/jackett/proxy.test.js new file mode 100644 index 000000000..c2d8d0302 --- /dev/null +++ b/src/widgets/jackett/proxy.test.js @@ -0,0 +1,79 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import createMockRes from "test-utils/create-mock-res"; + +const { httpProxy, getServiceWidget, logger } = vi.hoisted(() => ({ + httpProxy: vi.fn(), + getServiceWidget: vi.fn(), + logger: { + error: vi.fn(), + }, +})); + +vi.mock("utils/logger", () => ({ + default: () => logger, +})); +vi.mock("utils/config/service-helpers", () => ({ + default: getServiceWidget, +})); +vi.mock("utils/proxy/http", () => ({ + httpProxy, +})); +vi.mock("widgets/widgets", () => ({ + default: { + jackett: { + api: "{url}/{endpoint}", + loginURL: "{url}/UI/Dashboard", + }, + }, +})); + +import jackettProxyHandler from "./proxy"; + +describe("widgets/jackett/proxy", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("fetches an auth cookie when password is set and passes it on requests", async () => { + getServiceWidget.mockResolvedValue({ + type: "jackett", + url: "http://jackett", + password: "pw", + }); + + httpProxy + // login cookie fetch + .mockResolvedValueOnce([200, "text/plain", null, null, { headers: { Cookie: "c=1" } }]) + // api call + .mockResolvedValueOnce([200, "application/json", Buffer.from("ok")]); + + const req = { query: { group: "g", service: "svc", endpoint: "api/v2.0/indexers/all/results", index: "0" } }; + const res = createMockRes(); + + await jackettProxyHandler(req, res); + + expect(httpProxy).toHaveBeenCalledTimes(2); + expect(httpProxy.mock.calls[1][1].headers.Cookie).toBe("c=1"); + expect(res.statusCode).toBe(200); + expect(res.body).toEqual(Buffer.from("ok")); + }); + + it("returns 500 when cookie authentication fails", async () => { + getServiceWidget.mockResolvedValue({ + type: "jackett", + url: "http://jackett", + password: "pw", + }); + + httpProxy.mockResolvedValueOnce([200, "text/plain", null, null, { headers: {} }]); + + const req = { query: { group: "g", service: "svc", endpoint: "api", index: "0" } }; + const res = createMockRes(); + + await jackettProxyHandler(req, res); + + expect(res.statusCode).toBe(500); + expect(res.body).toEqual({ error: "Failed to authenticate with Jackett" }); + }); +}); diff --git a/src/widgets/omada/proxy.test.js b/src/widgets/omada/proxy.test.js new file mode 100644 index 000000000..06690df0a --- /dev/null +++ b/src/widgets/omada/proxy.test.js @@ -0,0 +1,121 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import createMockRes from "test-utils/create-mock-res"; + +const { httpProxy, getServiceWidget, logger } = vi.hoisted(() => ({ + httpProxy: vi.fn(), + getServiceWidget: vi.fn(), + logger: { + debug: vi.fn(), + error: vi.fn(), + }, +})); + +vi.mock("utils/logger", () => ({ + default: () => logger, +})); +vi.mock("utils/config/service-helpers", () => ({ + default: getServiceWidget, +})); +vi.mock("utils/proxy/http", () => ({ + httpProxy, +})); + +import omadaProxyHandler from "./proxy"; + +describe("widgets/omada/proxy", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("fetches controller info, logs in, selects site, and returns overview stats (v4)", async () => { + getServiceWidget.mockResolvedValue({ + url: "http://omada", + username: "u", + password: "p", + site: "Default", + }); + + httpProxy + // controller info + .mockResolvedValueOnce([ + 200, + "application/json", + JSON.stringify({ result: { omadacId: "cid", controllerVer: "4.5.6" } }), + ]) + // login + .mockResolvedValueOnce([ + 200, + "application/json", + Buffer.from(JSON.stringify({ errorCode: 0, result: { token: "t" } })), + ]) + // sites list + .mockResolvedValueOnce([ + 200, + "application/json", + JSON.stringify({ errorCode: 0, result: { data: [{ name: "Default", key: "sitekey" }] } }), + ]) + // overview diagram + .mockResolvedValueOnce([ + 200, + "application/json", + JSON.stringify({ + errorCode: 0, + result: { + totalClientNum: 10, + connectedApNum: 2, + connectedGatewayNum: 1, + connectedSwitchNum: 3, + }, + }), + ]) + // alert count + .mockResolvedValueOnce([200, "application/json", JSON.stringify({ errorCode: 0, result: { alertNum: 4 } })]); + + const req = { query: { group: "g", service: "svc", index: "0" } }; + const res = createMockRes(); + + await omadaProxyHandler(req, res); + + expect(httpProxy).toHaveBeenCalledTimes(5); + expect(res.statusCode).toBe(null); // uses res.send directly without setting status + expect(res.body).toBe( + JSON.stringify({ + connectedAp: 2, + activeUser: 10, + alerts: 4, + connectedGateways: 1, + connectedSwitches: 3, + }), + ); + }); + + it("returns an error when the site is not found", async () => { + getServiceWidget.mockResolvedValue({ url: "http://omada", username: "u", password: "p", site: "Missing" }); + + httpProxy + .mockResolvedValueOnce([ + 200, + "application/json", + JSON.stringify({ result: { omadacId: "cid", controllerVer: "4.5.6" } }), + ]) + .mockResolvedValueOnce([ + 200, + "application/json", + Buffer.from(JSON.stringify({ errorCode: 0, result: { token: "t" } })), + ]) + .mockResolvedValueOnce([ + 200, + "application/json", + JSON.stringify({ errorCode: 0, result: { data: [{ name: "Default", key: "sitekey" }] } }), + ]); + + const req = { query: { group: "g", service: "svc", index: "0" } }; + const res = createMockRes(); + + await omadaProxyHandler(req, res); + + expect(res.statusCode).toBe(200); + expect(res.body.error.message).toContain("Site Missing is not found"); + }); +}); diff --git a/src/widgets/openmediavault/proxy.test.js b/src/widgets/openmediavault/proxy.test.js new file mode 100644 index 000000000..ef94621ea --- /dev/null +++ b/src/widgets/openmediavault/proxy.test.js @@ -0,0 +1,74 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import createMockRes from "test-utils/create-mock-res"; + +const { httpProxy, getServiceWidget, cookieJar, logger } = vi.hoisted(() => ({ + httpProxy: vi.fn(), + getServiceWidget: vi.fn(), + cookieJar: { + addCookieToJar: vi.fn(), + setCookieHeader: vi.fn(), + }, + logger: { + debug: vi.fn(), + error: vi.fn(), + }, +})); + +vi.mock("utils/logger", () => ({ + default: () => logger, +})); +vi.mock("utils/config/service-helpers", () => ({ + default: getServiceWidget, +})); +vi.mock("utils/proxy/http", () => ({ + httpProxy, +})); +vi.mock("utils/proxy/cookie-jar", () => cookieJar); +vi.mock("widgets/widgets", () => ({ + default: { + openmediavault: { + api: "{url}/rpc.php", + }, + }, +})); + +import openmediavaultProxyHandler from "./proxy"; + +describe("widgets/openmediavault/proxy", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("logs in after a 401 and retries the RPC call", async () => { + getServiceWidget.mockResolvedValue({ + type: "openmediavault", + url: "http://omv", + username: "u", + password: "p", + method: "foo.bar", + }); + + httpProxy + // initial rpc unauthorized + .mockResolvedValueOnce([401, "application/json", Buffer.from(JSON.stringify({ response: {} })), {}]) + // login rpc + .mockResolvedValueOnce([ + 200, + "application/json", + Buffer.from(JSON.stringify({ response: { authenticated: true } })), + { "set-cookie": ["sid=1"] }, + ]) + // retry rpc + .mockResolvedValueOnce([200, "application/json", Buffer.from(JSON.stringify({ response: { ok: true } })), {}]); + + const req = { query: { group: "g", service: "svc", index: "0" } }; + const res = createMockRes(); + + await openmediavaultProxyHandler(req, res); + + expect(cookieJar.addCookieToJar).toHaveBeenCalled(); + expect(res.statusCode).toBe(200); + expect(res.body).toEqual(Buffer.from(JSON.stringify({ response: { ok: true } }))); + }); +}); diff --git a/src/widgets/openwrt/proxy.test.js b/src/widgets/openwrt/proxy.test.js new file mode 100644 index 000000000..3e1bf9d49 --- /dev/null +++ b/src/widgets/openwrt/proxy.test.js @@ -0,0 +1,59 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import createMockRes from "test-utils/create-mock-res"; + +const { sendJsonRpcRequest, getServiceWidget, logger } = vi.hoisted(() => ({ + sendJsonRpcRequest: vi.fn(), + getServiceWidget: vi.fn(), + logger: { debug: vi.fn() }, +})); + +vi.mock("utils/logger", () => ({ + default: () => logger, +})); +vi.mock("utils/config/service-helpers", () => ({ + default: getServiceWidget, +})); +vi.mock("utils/proxy/handlers/jsonrpc", () => ({ + sendJsonRpcRequest, +})); +vi.mock("widgets/widgets", () => ({ + default: { + openwrt: { + api: "{url}", + }, + }, +})); + +import openwrtProxyHandler from "./proxy"; + +describe("widgets/openwrt/proxy", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("logs in and retries after an unauthorized response", async () => { + getServiceWidget.mockResolvedValue({ type: "openwrt", url: "http://openwrt", username: "u", password: "p" }); + + sendJsonRpcRequest + // initial call -> unauthorized + .mockResolvedValueOnce([200, "application/json", Buffer.from(JSON.stringify({ error: { code: -32002 } }))]) + // login -> sets ubus token + .mockResolvedValueOnce([200, "application/json", Buffer.from(JSON.stringify([0, { ubus_rpc_session: "sess" }]))]) + // retry system info -> ok + .mockResolvedValueOnce([ + 200, + "application/json", + Buffer.from(JSON.stringify([0, { uptime: 1, load: [0, 131072, 0] }])), + ]); + + const req = { query: { group: "g", service: "svc", index: "0" } }; + const res = createMockRes(); + + await openwrtProxyHandler(req, res); + + expect(sendJsonRpcRequest).toHaveBeenCalledTimes(3); + expect(res.statusCode).toBe(200); + expect(JSON.parse(res.body).cpuLoad).toBe("2.00"); + }); +}); diff --git a/src/widgets/photoprism/proxy.test.js b/src/widgets/photoprism/proxy.test.js new file mode 100644 index 000000000..efeb67c91 --- /dev/null +++ b/src/widgets/photoprism/proxy.test.js @@ -0,0 +1,41 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import createMockRes from "test-utils/create-mock-res"; + +const { httpProxy, getServiceWidget, logger } = vi.hoisted(() => ({ + httpProxy: vi.fn(), + getServiceWidget: vi.fn(), + logger: { debug: vi.fn(), error: vi.fn() }, +})); + +vi.mock("utils/logger", () => ({ + default: () => logger, +})); +vi.mock("utils/config/service-helpers", () => ({ + default: getServiceWidget, +})); +vi.mock("utils/proxy/http", () => ({ + httpProxy, +})); + +import photoprismProxyHandler from "./proxy"; + +describe("widgets/photoprism/proxy", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("supports bearer-token auth and returns config count", async () => { + getServiceWidget.mockResolvedValue({ url: "http://pp", key: "k" }); + httpProxy.mockResolvedValueOnce([200, "application/json", Buffer.from(JSON.stringify({ config: { count: 123 } }))]); + + const req = { query: { group: "g", service: "svc", index: "0" } }; + const res = createMockRes(); + + await photoprismProxyHandler(req, res); + + expect(httpProxy.mock.calls[0][1].headers.Authorization).toBe("Bearer k"); + expect(res.statusCode).toBe(200); + expect(res.body).toBe(123); + }); +}); diff --git a/src/widgets/plex/proxy.test.js b/src/widgets/plex/proxy.test.js new file mode 100644 index 000000000..770946d78 --- /dev/null +++ b/src/widgets/plex/proxy.test.js @@ -0,0 +1,93 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import createMockRes from "test-utils/create-mock-res"; + +const { httpProxy, getServiceWidget, cache, xml2json, logger } = vi.hoisted(() => { + const store = new Map(); + return { + httpProxy: vi.fn(), + getServiceWidget: vi.fn(), + cache: { + get: vi.fn((k) => (store.has(k) ? store.get(k) : null)), + put: vi.fn((k, v) => store.set(k, v)), + del: vi.fn((k) => store.delete(k)), + _reset: () => store.clear(), + }, + xml2json: vi.fn((xml) => { + if (xml === "sessions") return JSON.stringify({ MediaContainer: { _attributes: { size: "2" } } }); + if (xml === "libraries") + return JSON.stringify({ + MediaContainer: { + Directory: [ + { _attributes: { type: "movie", key: "1" } }, + { _attributes: { type: "show", key: "2" } }, + { _attributes: { type: "artist", key: "3" } }, + ], + }, + }); + if (xml === "movies") return JSON.stringify({ MediaContainer: { _attributes: { size: "10" } } }); + if (xml === "tv") return JSON.stringify({ MediaContainer: { _attributes: { totalSize: "20" } } }); + if (xml === "albums") return JSON.stringify({ MediaContainer: { _attributes: { size: "30" } } }); + return JSON.stringify({ MediaContainer: { _attributes: { size: "0" } } }); + }), + logger: { debug: vi.fn(), error: vi.fn() }, + }; +}); + +vi.mock("utils/logger", () => ({ + default: () => logger, +})); +vi.mock("utils/config/service-helpers", () => ({ + default: getServiceWidget, +})); +vi.mock("utils/proxy/http", () => ({ + httpProxy, +})); +vi.mock("memory-cache", () => ({ + default: cache, + ...cache, +})); +vi.mock("xml-js", () => ({ + xml2json, +})); +vi.mock("widgets/widgets", () => ({ + default: { + plex: { + api: "{url}{endpoint}", + }, + }, +})); + +import plexProxyHandler from "./proxy"; + +describe("widgets/plex/proxy", () => { + beforeEach(() => { + vi.clearAllMocks(); + cache._reset(); + }); + + it("fetches sessions and library counts, caching intermediate results", async () => { + getServiceWidget.mockResolvedValue({ type: "plex", url: "http://plex" }); + + httpProxy + // sessions + .mockResolvedValueOnce([200, "application/xml", Buffer.from("sessions")]) + // libraries + .mockResolvedValueOnce([200, "application/xml", Buffer.from("libraries")]) + // movies + .mockResolvedValueOnce([200, "application/xml", Buffer.from("movies")]) + // tv + .mockResolvedValueOnce([200, "application/xml", Buffer.from("tv")]) + // albums + .mockResolvedValueOnce([200, "application/xml", Buffer.from("albums")]); + + const req = { query: { group: "g", service: "svc", index: "0" } }; + const res = createMockRes(); + + await plexProxyHandler(req, res); + + expect(res.statusCode).toBe(200); + expect(res.body).toEqual({ streams: "2", albums: 30, movies: 10, tv: 20 }); + expect(cache.put).toHaveBeenCalled(); + }); +}); diff --git a/src/widgets/rutorrent/proxy.test.js b/src/widgets/rutorrent/proxy.test.js new file mode 100644 index 000000000..f9d785f4c --- /dev/null +++ b/src/widgets/rutorrent/proxy.test.js @@ -0,0 +1,48 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import createMockRes from "test-utils/create-mock-res"; + +const { httpProxy, getServiceWidget, logger } = vi.hoisted(() => ({ + httpProxy: vi.fn(), + getServiceWidget: vi.fn(), + logger: { error: vi.fn() }, +})); + +vi.mock("utils/logger", () => ({ + default: () => logger, +})); +vi.mock("utils/config/service-helpers", () => ({ + default: getServiceWidget, +})); +vi.mock("utils/proxy/http", () => ({ + httpProxy, +})); +vi.mock("widgets/widgets", () => ({ + default: { + rutorrent: { + api: "{url}", + }, + }, +})); + +import rutorrentProxyHandler from "./proxy"; + +describe("widgets/rutorrent/proxy", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("parses torrent list data into an array", async () => { + getServiceWidget.mockResolvedValue({ type: "rutorrent", url: "http://ru", username: "u", password: "p" }); + httpProxy.mockResolvedValueOnce([200, "application/json", JSON.stringify({ t: { hash1: Array(34).fill(0) } })]); + + const req = { query: { group: "g", service: "svc", index: "0" } }; + const res = createMockRes(); + + await rutorrentProxyHandler(req, res); + + expect(res.statusCode).toBe(200); + expect(Array.isArray(res.body)).toBe(true); + expect(res.body[0]["d.get_name"]).toBe(0); + }); +});