mirror of
https://github.com/gethomepage/homepage.git
synced 2026-02-08 00:40:52 +08:00
test: cover info widget API routes
This commit is contained in:
76
src/pages/api/widgets/glances.test.js
Normal file
76
src/pages/api/widgets/glances.test.js
Normal file
@@ -0,0 +1,76 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import createMockRes from "test-utils/create-mock-res";
|
||||
|
||||
const { getPrivateWidgetOptions, httpProxy, logger } = vi.hoisted(() => ({
|
||||
getPrivateWidgetOptions: vi.fn(),
|
||||
httpProxy: vi.fn(),
|
||||
logger: { error: vi.fn() },
|
||||
}));
|
||||
|
||||
vi.mock("utils/config/widget-helpers", () => ({
|
||||
getPrivateWidgetOptions,
|
||||
}));
|
||||
|
||||
vi.mock("utils/proxy/http", () => ({
|
||||
httpProxy,
|
||||
}));
|
||||
|
||||
vi.mock("utils/logger", () => ({
|
||||
default: () => logger,
|
||||
}));
|
||||
|
||||
import handler from "./glances";
|
||||
|
||||
describe("pages/api/widgets/glances", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("returns 400 when the widget URL is missing", async () => {
|
||||
getPrivateWidgetOptions.mockResolvedValueOnce({});
|
||||
|
||||
const req = { query: { index: "0" } };
|
||||
const res = createMockRes();
|
||||
|
||||
await handler(req, res);
|
||||
|
||||
expect(res.statusCode).toBe(400);
|
||||
expect(res.body.error).toBe("Missing Glances URL");
|
||||
});
|
||||
|
||||
it("returns cpu/load/mem and includes optional endpoints when requested", async () => {
|
||||
getPrivateWidgetOptions.mockResolvedValueOnce({ url: "http://glances", username: "u", password: "p" });
|
||||
|
||||
httpProxy
|
||||
.mockResolvedValueOnce([200, null, Buffer.from(JSON.stringify({ total: 1 }))]) // cpu
|
||||
.mockResolvedValueOnce([200, null, Buffer.from(JSON.stringify({ avg: 2 }))]) // load
|
||||
.mockResolvedValueOnce([200, null, Buffer.from(JSON.stringify({ available: 3 }))]) // mem
|
||||
.mockResolvedValueOnce([200, null, Buffer.from(JSON.stringify("1 days"))]) // uptime
|
||||
.mockResolvedValueOnce([200, null, Buffer.from(JSON.stringify([{ label: "cpu_thermal", value: 50 }]))]) // sensors
|
||||
.mockResolvedValueOnce([200, null, Buffer.from(JSON.stringify([{ mnt_point: "/", percent: 1 }]))]); // fs
|
||||
|
||||
const req = { query: { index: "0", uptime: "1", cputemp: "1", disk: "1", version: "4" } };
|
||||
const res = createMockRes();
|
||||
|
||||
await handler(req, res);
|
||||
|
||||
expect(httpProxy).toHaveBeenCalledWith(
|
||||
"http://glances/api/4/cpu",
|
||||
expect.objectContaining({
|
||||
method: "GET",
|
||||
headers: expect.objectContaining({ Authorization: expect.any(String) }),
|
||||
}),
|
||||
);
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(res.body).toEqual({
|
||||
cpu: { total: 1 },
|
||||
load: { avg: 2 },
|
||||
mem: { available: 3 },
|
||||
uptime: "1 days",
|
||||
sensors: [{ label: "cpu_thermal", value: 50 }],
|
||||
fs: [{ mnt_point: "/", percent: 1 }],
|
||||
});
|
||||
});
|
||||
});
|
||||
30
src/pages/api/widgets/index.test.js
Normal file
30
src/pages/api/widgets/index.test.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import createMockRes from "test-utils/create-mock-res";
|
||||
|
||||
const { widgetsResponse } = vi.hoisted(() => ({
|
||||
widgetsResponse: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("utils/config/api-response", () => ({
|
||||
widgetsResponse,
|
||||
}));
|
||||
|
||||
import handler from "./index";
|
||||
|
||||
describe("pages/api/widgets/index", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("returns widgetsResponse()", async () => {
|
||||
widgetsResponse.mockResolvedValueOnce([{ type: "logo", options: {} }]);
|
||||
|
||||
const req = { query: {} };
|
||||
const res = createMockRes();
|
||||
|
||||
await handler(req, res);
|
||||
|
||||
expect(res.body).toEqual([{ type: "logo", options: {} }]);
|
||||
});
|
||||
});
|
||||
147
src/pages/api/widgets/kubernetes.test.js
Normal file
147
src/pages/api/widgets/kubernetes.test.js
Normal file
@@ -0,0 +1,147 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import createMockRes from "test-utils/create-mock-res";
|
||||
|
||||
const { kc, coreApi, metricsApi, getKubeConfig, parseCpu, parseMemory, logger } = vi.hoisted(() => {
|
||||
const coreApi = { listNode: vi.fn() };
|
||||
const metricsApi = { getNodeMetrics: vi.fn() };
|
||||
|
||||
const kc = {
|
||||
makeApiClient: vi.fn(() => coreApi),
|
||||
};
|
||||
|
||||
return {
|
||||
kc,
|
||||
coreApi,
|
||||
metricsApi,
|
||||
getKubeConfig: vi.fn(),
|
||||
parseCpu: vi.fn(),
|
||||
parseMemory: vi.fn(),
|
||||
logger: { error: vi.fn(), debug: vi.fn() },
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("@kubernetes/client-node", () => ({
|
||||
CoreV1Api: class CoreV1Api {},
|
||||
Metrics: class Metrics {
|
||||
constructor() {
|
||||
return metricsApi;
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("../../../utils/config/kubernetes", () => ({
|
||||
getKubeConfig,
|
||||
}));
|
||||
|
||||
vi.mock("../../../utils/kubernetes/utils", () => ({
|
||||
parseCpu,
|
||||
parseMemory,
|
||||
}));
|
||||
|
||||
vi.mock("../../../utils/logger", () => ({
|
||||
default: () => logger,
|
||||
}));
|
||||
|
||||
import handler from "./kubernetes";
|
||||
|
||||
describe("pages/api/widgets/kubernetes", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("returns 500 when no kube config is available", async () => {
|
||||
getKubeConfig.mockReturnValueOnce(null);
|
||||
|
||||
const req = { query: {} };
|
||||
const res = createMockRes();
|
||||
|
||||
await handler(req, res);
|
||||
|
||||
expect(res.statusCode).toBe(500);
|
||||
expect(res.body.error).toBe("No kubernetes configuration");
|
||||
});
|
||||
|
||||
it("returns 500 when listing nodes fails", async () => {
|
||||
getKubeConfig.mockReturnValueOnce(kc);
|
||||
coreApi.listNode.mockResolvedValueOnce(null);
|
||||
|
||||
const req = { query: {} };
|
||||
const res = createMockRes();
|
||||
|
||||
await handler(req, res);
|
||||
|
||||
expect(res.statusCode).toBe(500);
|
||||
expect(res.body.error).toContain("fetching nodes");
|
||||
});
|
||||
|
||||
it("returns 500 when metrics lookup fails", async () => {
|
||||
getKubeConfig.mockReturnValueOnce(kc);
|
||||
parseMemory.mockReturnValue(100);
|
||||
coreApi.listNode.mockResolvedValueOnce({
|
||||
items: [
|
||||
{
|
||||
metadata: { name: "n1" },
|
||||
status: { capacity: { cpu: "1", memory: "100" }, conditions: [{ type: "Ready", status: "True" }] },
|
||||
},
|
||||
],
|
||||
});
|
||||
metricsApi.getNodeMetrics.mockRejectedValueOnce(new Error("nope"));
|
||||
|
||||
const req = { query: {} };
|
||||
const res = createMockRes();
|
||||
|
||||
await handler(req, res);
|
||||
|
||||
expect(res.statusCode).toBe(500);
|
||||
expect(res.body.error).toContain("Error getting metrics");
|
||||
});
|
||||
|
||||
it("returns cluster totals and per-node usage", async () => {
|
||||
getKubeConfig.mockReturnValueOnce(kc);
|
||||
|
||||
parseMemory.mockImplementation((value) => {
|
||||
if (value === "100") return 100;
|
||||
if (value === "50") return 50;
|
||||
if (value === "30") return 30;
|
||||
return 0;
|
||||
});
|
||||
parseCpu.mockImplementation((value) => {
|
||||
if (value === "100m") return 0.1;
|
||||
if (value === "200m") return 0.2;
|
||||
return 0;
|
||||
});
|
||||
|
||||
coreApi.listNode.mockResolvedValueOnce({
|
||||
items: [
|
||||
{
|
||||
metadata: { name: "n1" },
|
||||
status: { capacity: { cpu: "1", memory: "100" }, conditions: [{ type: "Ready", status: "True" }] },
|
||||
},
|
||||
{
|
||||
metadata: { name: "n2" },
|
||||
status: { capacity: { cpu: "2", memory: "50" }, conditions: [{ type: "Ready", status: "False" }] },
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
metricsApi.getNodeMetrics.mockResolvedValueOnce({
|
||||
items: [
|
||||
{ metadata: { name: "n1" }, usage: { cpu: "100m", memory: "30" } },
|
||||
{ metadata: { name: "n2" }, usage: { cpu: "200m", memory: "50" } },
|
||||
],
|
||||
});
|
||||
|
||||
const req = { query: {} };
|
||||
const res = createMockRes();
|
||||
|
||||
await handler(req, res);
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(res.body.cluster.cpu.total).toBe(3);
|
||||
expect(res.body.cluster.cpu.load).toBeCloseTo(0.3);
|
||||
expect(res.body.cluster.memory.total).toBe(150);
|
||||
expect(res.body.nodes).toHaveLength(2);
|
||||
expect(res.body.nodes.find((n) => n.name === "n1").cpu.percent).toBeCloseTo(10);
|
||||
});
|
||||
});
|
||||
87
src/pages/api/widgets/longhorn.test.js
Normal file
87
src/pages/api/widgets/longhorn.test.js
Normal file
@@ -0,0 +1,87 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import createMockRes from "test-utils/create-mock-res";
|
||||
|
||||
const { getSettings, httpProxy, logger } = vi.hoisted(() => ({
|
||||
getSettings: vi.fn(),
|
||||
httpProxy: vi.fn(),
|
||||
logger: { error: vi.fn() },
|
||||
}));
|
||||
|
||||
vi.mock("../../../utils/config/config", () => ({
|
||||
getSettings,
|
||||
}));
|
||||
|
||||
vi.mock("../../../utils/proxy/http", () => ({
|
||||
httpProxy,
|
||||
}));
|
||||
|
||||
vi.mock("../../../utils/logger", () => ({
|
||||
default: () => logger,
|
||||
}));
|
||||
|
||||
import handler from "./longhorn";
|
||||
|
||||
describe("pages/api/widgets/longhorn", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("returns 400 when the longhorn URL isn't configured", async () => {
|
||||
getSettings.mockReturnValueOnce({ providers: { longhorn: {} } });
|
||||
|
||||
const req = { query: {} };
|
||||
const res = createMockRes();
|
||||
|
||||
await handler(req, res);
|
||||
|
||||
expect(res.statusCode).toBe(400);
|
||||
expect(res.body.error).toBe("Missing Longhorn URL");
|
||||
});
|
||||
|
||||
it("parses and aggregates node disk totals, including a total node", async () => {
|
||||
getSettings.mockReturnValueOnce({
|
||||
providers: { longhorn: { url: "http://lh", username: "u", password: "p" } },
|
||||
});
|
||||
|
||||
const payload = {
|
||||
data: [
|
||||
{
|
||||
id: "n1",
|
||||
disks: {
|
||||
d1: { storageAvailable: 1, storageMaximum: 10, storageReserved: 2, storageScheduled: 3 },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "n2",
|
||||
disks: {
|
||||
d1: { storageAvailable: 4, storageMaximum: 20, storageReserved: 5, storageScheduled: 6 },
|
||||
d2: { storageAvailable: 1, storageMaximum: 1, storageReserved: 1, storageScheduled: 1 },
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
httpProxy.mockResolvedValueOnce([200, "application/json", JSON.stringify(payload)]);
|
||||
|
||||
const req = { query: {} };
|
||||
const res = createMockRes();
|
||||
|
||||
await handler(req, res);
|
||||
|
||||
expect(res.headers["Content-Type"]).toBe("application/json");
|
||||
expect(res.statusCode).toBe(200);
|
||||
|
||||
const nodes = res.body.nodes;
|
||||
expect(nodes.map((n) => n.id)).toEqual(["n1", "n2", "total"]);
|
||||
expect(nodes.find((n) => n.id === "total")).toEqual(
|
||||
expect.objectContaining({
|
||||
id: "total",
|
||||
available: 6,
|
||||
maximum: 31,
|
||||
reserved: 8,
|
||||
scheduled: 10,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
52
src/pages/api/widgets/openmeteo.test.js
Normal file
52
src/pages/api/widgets/openmeteo.test.js
Normal file
@@ -0,0 +1,52 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import createMockRes from "test-utils/create-mock-res";
|
||||
|
||||
const { cachedRequest } = vi.hoisted(() => ({
|
||||
cachedRequest: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("utils/proxy/http", () => ({
|
||||
cachedRequest,
|
||||
}));
|
||||
|
||||
import handler from "./openmeteo";
|
||||
|
||||
describe("pages/api/widgets/openmeteo", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("builds the open-meteo URL with units + timezone and calls cachedRequest", async () => {
|
||||
cachedRequest.mockResolvedValueOnce({ ok: true });
|
||||
|
||||
const req = {
|
||||
query: { latitude: "1", longitude: "2", units: "metric", cache: "5" },
|
||||
};
|
||||
const res = createMockRes();
|
||||
|
||||
await handler(req, res);
|
||||
|
||||
expect(cachedRequest).toHaveBeenCalledWith(
|
||||
"https://api.open-meteo.com/v1/forecast?latitude=1&longitude=2&daily=sunrise,sunset¤t_weather=true&temperature_unit=celsius&timezone=auto",
|
||||
"5",
|
||||
);
|
||||
expect(res.body).toEqual({ ok: true });
|
||||
});
|
||||
|
||||
it("uses the provided timezone and fahrenheit for non-metric units", async () => {
|
||||
cachedRequest.mockResolvedValueOnce({ ok: true });
|
||||
|
||||
const req = {
|
||||
query: { latitude: "1", longitude: "2", units: "imperial", cache: 1, timezone: "UTC" },
|
||||
};
|
||||
const res = createMockRes();
|
||||
|
||||
await handler(req, res);
|
||||
|
||||
expect(cachedRequest).toHaveBeenCalledWith(
|
||||
"https://api.open-meteo.com/v1/forecast?latitude=1&longitude=2&daily=sunrise,sunset¤t_weather=true&temperature_unit=fahrenheit&timezone=UTC",
|
||||
1,
|
||||
);
|
||||
});
|
||||
});
|
||||
99
src/pages/api/widgets/openweathermap.test.js
Normal file
99
src/pages/api/widgets/openweathermap.test.js
Normal file
@@ -0,0 +1,99 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import createMockRes from "test-utils/create-mock-res";
|
||||
|
||||
const { getSettings, getPrivateWidgetOptions, cachedRequest } = vi.hoisted(() => ({
|
||||
getSettings: vi.fn(),
|
||||
getPrivateWidgetOptions: vi.fn(),
|
||||
cachedRequest: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("utils/config/config", () => ({
|
||||
getSettings,
|
||||
}));
|
||||
|
||||
vi.mock("utils/config/widget-helpers", () => ({
|
||||
getPrivateWidgetOptions,
|
||||
}));
|
||||
|
||||
vi.mock("utils/proxy/http", () => ({
|
||||
cachedRequest,
|
||||
}));
|
||||
|
||||
import handler from "./openweathermap";
|
||||
|
||||
describe("pages/api/widgets/openweathermap", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("returns 400 when no API key and no provider are supplied", async () => {
|
||||
getPrivateWidgetOptions.mockResolvedValueOnce({});
|
||||
|
||||
const req = { query: { latitude: "1", longitude: "2", units: "metric", lang: "en", index: "0" } };
|
||||
const res = createMockRes();
|
||||
|
||||
await handler(req, res);
|
||||
|
||||
expect(res.statusCode).toBe(400);
|
||||
expect(res.body).toEqual({ error: "Missing API key or provider" });
|
||||
});
|
||||
|
||||
it("returns 400 when provider doesn't match endpoint and no per-widget key exists", async () => {
|
||||
getPrivateWidgetOptions.mockResolvedValueOnce({});
|
||||
|
||||
const req = { query: { latitude: "1", longitude: "2", units: "metric", lang: "en", provider: "weatherapi" } };
|
||||
const res = createMockRes();
|
||||
|
||||
await handler(req, res);
|
||||
|
||||
expect(res.statusCode).toBe(400);
|
||||
expect(res.body).toEqual({ error: "Invalid provider for endpoint" });
|
||||
});
|
||||
|
||||
it("uses key from widget options when present", async () => {
|
||||
getPrivateWidgetOptions.mockResolvedValueOnce({ apiKey: "from-widget" });
|
||||
cachedRequest.mockResolvedValueOnce({ ok: true });
|
||||
|
||||
const req = {
|
||||
query: { latitude: "1", longitude: "2", units: "metric", lang: "en", cache: "1", index: "2" },
|
||||
};
|
||||
const res = createMockRes();
|
||||
|
||||
await handler(req, res);
|
||||
|
||||
expect(getPrivateWidgetOptions).toHaveBeenCalledWith("openweathermap", "2");
|
||||
expect(cachedRequest).toHaveBeenCalledWith(
|
||||
"https://api.openweathermap.org/data/2.5/weather?lat=1&lon=2&appid=from-widget&units=metric&lang=en",
|
||||
"1",
|
||||
);
|
||||
expect(res.body).toEqual({ ok: true });
|
||||
});
|
||||
|
||||
it("falls back to settings provider key when provider=openweathermap", async () => {
|
||||
getPrivateWidgetOptions.mockResolvedValueOnce({});
|
||||
getSettings.mockReturnValueOnce({ providers: { openweathermap: "from-settings" } });
|
||||
cachedRequest.mockResolvedValueOnce({ ok: true });
|
||||
|
||||
const req = {
|
||||
query: {
|
||||
latitude: "1",
|
||||
longitude: "2",
|
||||
units: "imperial",
|
||||
lang: "en",
|
||||
provider: "openweathermap",
|
||||
cache: 2,
|
||||
index: "0",
|
||||
},
|
||||
};
|
||||
const res = createMockRes();
|
||||
|
||||
await handler(req, res);
|
||||
|
||||
expect(cachedRequest).toHaveBeenCalledWith(
|
||||
"https://api.openweathermap.org/data/2.5/weather?lat=1&lon=2&appid=from-settings&units=imperial&lang=en",
|
||||
2,
|
||||
);
|
||||
expect(res.body).toEqual({ ok: true });
|
||||
});
|
||||
});
|
||||
@@ -1,9 +1,9 @@
|
||||
import si from "systeminformation";
|
||||
|
||||
import createLogger from "utils/logger";
|
||||
|
||||
const logger = createLogger("resources");
|
||||
|
||||
const si = require("systeminformation");
|
||||
|
||||
export default async function handler(req, res) {
|
||||
const { type, target, interfaceName = "default" } = req.query;
|
||||
|
||||
|
||||
127
src/pages/api/widgets/resources.test.js
Normal file
127
src/pages/api/widgets/resources.test.js
Normal file
@@ -0,0 +1,127 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import createMockRes from "test-utils/create-mock-res";
|
||||
|
||||
const { si, logger } = vi.hoisted(() => ({
|
||||
si: {
|
||||
currentLoad: vi.fn(),
|
||||
fsSize: vi.fn(),
|
||||
mem: vi.fn(),
|
||||
cpuTemperature: vi.fn(),
|
||||
time: vi.fn(),
|
||||
networkStats: vi.fn(),
|
||||
networkInterfaceDefault: vi.fn(),
|
||||
},
|
||||
logger: {
|
||||
debug: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("utils/logger", () => ({
|
||||
default: () => logger,
|
||||
}));
|
||||
|
||||
vi.mock("systeminformation", () => ({ default: si }));
|
||||
|
||||
import handler from "./resources";
|
||||
|
||||
describe("pages/api/widgets/resources", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("returns CPU load data", async () => {
|
||||
si.currentLoad.mockResolvedValueOnce({ currentLoad: 12.34, avgLoad: 1.23 });
|
||||
|
||||
const req = { query: { type: "cpu" } };
|
||||
const res = createMockRes();
|
||||
|
||||
await handler(req, res);
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(res.body.cpu).toEqual({ usage: 12.34, load: 1.23 });
|
||||
});
|
||||
|
||||
it("returns 404 when requested disk target does not exist", async () => {
|
||||
si.fsSize.mockResolvedValueOnce([{ mount: "/" }]);
|
||||
|
||||
const req = { query: { type: "disk", target: "/missing" } };
|
||||
const res = createMockRes();
|
||||
|
||||
await handler(req, res);
|
||||
|
||||
expect(res.statusCode).toBe(404);
|
||||
expect(res.body).toEqual({ error: "Resource not available." });
|
||||
expect(logger.warn).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("returns disk info for the requested mount", async () => {
|
||||
si.fsSize.mockResolvedValueOnce([{ mount: "/data", size: 1 }]);
|
||||
|
||||
const req = { query: { type: "disk", target: "/data" } };
|
||||
const res = createMockRes();
|
||||
|
||||
await handler(req, res);
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(res.body.drive).toEqual({ mount: "/data", size: 1 });
|
||||
});
|
||||
|
||||
it("returns memory, cpu temp and uptime", async () => {
|
||||
si.mem.mockResolvedValueOnce({ total: 10 });
|
||||
si.cpuTemperature.mockResolvedValueOnce({ main: 50 });
|
||||
si.time.mockResolvedValueOnce({ uptime: 123 });
|
||||
|
||||
const resMem = createMockRes();
|
||||
await handler({ query: { type: "memory" } }, resMem);
|
||||
expect(resMem.statusCode).toBe(200);
|
||||
expect(resMem.body.memory).toEqual({ total: 10 });
|
||||
|
||||
const resTemp = createMockRes();
|
||||
await handler({ query: { type: "cputemp" } }, resTemp);
|
||||
expect(resTemp.statusCode).toBe(200);
|
||||
expect(resTemp.body.cputemp).toEqual({ main: 50 });
|
||||
|
||||
const resUptime = createMockRes();
|
||||
await handler({ query: { type: "uptime" } }, resUptime);
|
||||
expect(resUptime.statusCode).toBe(200);
|
||||
expect(resUptime.body.uptime).toBe(123);
|
||||
});
|
||||
|
||||
it("returns 404 when requested network interface does not exist", async () => {
|
||||
si.networkStats.mockResolvedValueOnce([{ iface: "en0" }]);
|
||||
|
||||
const req = { query: { type: "network", interfaceName: "missing" } };
|
||||
const res = createMockRes();
|
||||
|
||||
await handler(req, res);
|
||||
|
||||
expect(res.statusCode).toBe(404);
|
||||
expect(res.body).toEqual({ error: "Interface not found" });
|
||||
});
|
||||
|
||||
it("returns default interface network stats", async () => {
|
||||
si.networkStats.mockResolvedValueOnce([{ iface: "en0", rx_bytes: 1 }]);
|
||||
si.networkInterfaceDefault.mockResolvedValueOnce("en0");
|
||||
|
||||
const req = { query: { type: "network" } };
|
||||
const res = createMockRes();
|
||||
|
||||
await handler(req, res);
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(res.body.interface).toBe("en0");
|
||||
expect(res.body.network).toEqual({ iface: "en0", rx_bytes: 1 });
|
||||
});
|
||||
|
||||
it("returns 400 for an invalid type", async () => {
|
||||
const req = { query: { type: "nope" } };
|
||||
const res = createMockRes();
|
||||
|
||||
await handler(req, res);
|
||||
|
||||
expect(res.statusCode).toBe(400);
|
||||
expect(res.body).toEqual({ error: "invalid type" });
|
||||
});
|
||||
});
|
||||
82
src/pages/api/widgets/stocks.test.js
Normal file
82
src/pages/api/widgets/stocks.test.js
Normal file
@@ -0,0 +1,82 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import createMockRes from "test-utils/create-mock-res";
|
||||
|
||||
const { getSettings, cachedRequest, logger } = vi.hoisted(() => ({
|
||||
getSettings: vi.fn(),
|
||||
cachedRequest: vi.fn(),
|
||||
logger: { debug: vi.fn() },
|
||||
}));
|
||||
|
||||
vi.mock("utils/config/config", () => ({
|
||||
getSettings,
|
||||
}));
|
||||
|
||||
vi.mock("utils/proxy/http", () => ({
|
||||
cachedRequest,
|
||||
}));
|
||||
|
||||
vi.mock("utils/logger", () => ({
|
||||
default: () => logger,
|
||||
}));
|
||||
|
||||
import handler from "./stocks";
|
||||
|
||||
describe("pages/api/widgets/stocks", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("validates watchlist and provider", async () => {
|
||||
const res1 = createMockRes();
|
||||
await handler({ query: {} }, res1);
|
||||
expect(res1.statusCode).toBe(400);
|
||||
|
||||
const res2 = createMockRes();
|
||||
await handler({ query: { watchlist: "null", provider: "finnhub" } }, res2);
|
||||
expect(res2.statusCode).toBe(400);
|
||||
|
||||
const res3 = createMockRes();
|
||||
await handler({ query: { watchlist: "AAPL,AAPL", provider: "finnhub" } }, res3);
|
||||
expect(res3.statusCode).toBe(400);
|
||||
expect(res3.body.error).toContain("duplicates");
|
||||
|
||||
const res4 = createMockRes();
|
||||
await handler({ query: { watchlist: "AAPL", provider: "nope" } }, res4);
|
||||
expect(res4.statusCode).toBe(400);
|
||||
expect(res4.body.error).toContain("Invalid provider");
|
||||
});
|
||||
|
||||
it("returns 400 when API key isn't configured for provider", async () => {
|
||||
getSettings.mockReturnValueOnce({ providers: {} });
|
||||
|
||||
const req = { query: { watchlist: "AAPL", provider: "finnhub" } };
|
||||
const res = createMockRes();
|
||||
|
||||
await handler(req, res);
|
||||
|
||||
expect(res.statusCode).toBe(400);
|
||||
expect(res.body.error).toContain("API Key");
|
||||
});
|
||||
|
||||
it("returns a normalized stocks response and rounds values", async () => {
|
||||
getSettings.mockReturnValueOnce({ providers: { finnhub: "k" } });
|
||||
|
||||
cachedRequest
|
||||
.mockResolvedValueOnce({ c: 10.123, dp: -1.234 }) // AAPL
|
||||
.mockResolvedValueOnce({ c: null, dp: null }); // MSFT
|
||||
|
||||
const req = { query: { watchlist: "AAPL,MSFT", provider: "finnhub", cache: "1" } };
|
||||
const res = createMockRes();
|
||||
|
||||
await handler(req, res);
|
||||
|
||||
expect(cachedRequest).toHaveBeenCalledWith("https://finnhub.io/api/v1/quote?symbol=AAPL&token=k", "1");
|
||||
expect(res.body).toEqual({
|
||||
stocks: [
|
||||
{ ticker: "AAPL", currentPrice: "10.12", percentChange: -1.23 },
|
||||
{ ticker: "MSFT", currentPrice: null, percentChange: null },
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
73
src/pages/api/widgets/weather.test.js
Normal file
73
src/pages/api/widgets/weather.test.js
Normal file
@@ -0,0 +1,73 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import createMockRes from "test-utils/create-mock-res";
|
||||
|
||||
const { getSettings, getPrivateWidgetOptions, cachedRequest } = vi.hoisted(() => ({
|
||||
getSettings: vi.fn(),
|
||||
getPrivateWidgetOptions: vi.fn(),
|
||||
cachedRequest: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("utils/config/config", () => ({
|
||||
getSettings,
|
||||
}));
|
||||
|
||||
vi.mock("utils/config/widget-helpers", () => ({
|
||||
getPrivateWidgetOptions,
|
||||
}));
|
||||
|
||||
vi.mock("utils/proxy/http", () => ({
|
||||
cachedRequest,
|
||||
}));
|
||||
|
||||
import handler from "./weather";
|
||||
|
||||
describe("pages/api/widgets/weatherapi", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("returns 400 when no API key and no provider are supplied", async () => {
|
||||
getPrivateWidgetOptions.mockResolvedValueOnce({});
|
||||
|
||||
const req = { query: { latitude: "1", longitude: "2", lang: "en", index: "0" } };
|
||||
const res = createMockRes();
|
||||
|
||||
await handler(req, res);
|
||||
|
||||
expect(res.statusCode).toBe(400);
|
||||
expect(res.body).toEqual({ error: "Missing API key or provider" });
|
||||
});
|
||||
|
||||
it("uses key from widget options when present", async () => {
|
||||
getPrivateWidgetOptions.mockResolvedValueOnce({ apiKey: "from-widget" });
|
||||
cachedRequest.mockResolvedValueOnce({ ok: true });
|
||||
|
||||
const req = { query: { latitude: "1", longitude: "2", lang: "en", cache: 1, index: "0" } };
|
||||
const res = createMockRes();
|
||||
|
||||
await handler(req, res);
|
||||
|
||||
expect(cachedRequest).toHaveBeenCalledWith(
|
||||
"http://api.weatherapi.com/v1/current.json?q=1,2&key=from-widget&lang=en",
|
||||
1,
|
||||
);
|
||||
expect(res.body).toEqual({ ok: true });
|
||||
});
|
||||
|
||||
it("falls back to settings provider key when provider=weatherapi", async () => {
|
||||
getPrivateWidgetOptions.mockResolvedValueOnce({});
|
||||
getSettings.mockReturnValueOnce({ providers: { weatherapi: "from-settings" } });
|
||||
cachedRequest.mockResolvedValueOnce({ ok: true });
|
||||
|
||||
const req = { query: { latitude: "1", longitude: "2", lang: "en", provider: "weatherapi", cache: "2" } };
|
||||
const res = createMockRes();
|
||||
|
||||
await handler(req, res);
|
||||
|
||||
expect(cachedRequest).toHaveBeenCalledWith(
|
||||
"http://api.weatherapi.com/v1/current.json?q=1,2&key=from-settings&lang=en",
|
||||
"2",
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user