test: expand coverage for api-helpers and service-helpers

This commit is contained in:
shamoon
2026-02-04 12:39:22 -08:00
parent 5dcc3f2a75
commit 9661dbf5c8
2 changed files with 160 additions and 7 deletions

View File

@@ -7,6 +7,12 @@ const { state, fs, yaml, config, Docker, dockerCfg, kubeCfg, kubeApi } = vi.hois
dockerContainers: [],
kubeConfig: null,
kubeServices: [],
logger: {
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
},
};
const fs = {
@@ -72,12 +78,8 @@ vi.mock("utils/config/kubernetes", () => kubeCfg);
vi.mock("utils/kubernetes/export", () => ({ default: kubeApi }));
vi.mock("utils/logger", () => ({
default: vi.fn(() => ({
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
})),
// Keep a stable logger instance so tests don't depend on module re-imports.
default: vi.fn(() => state.logger),
}));
describe("utils/config/service-helpers", () => {
@@ -90,6 +92,126 @@ describe("utils/config/service-helpers", () => {
state.kubeServices = [];
});
it("servicesFromConfig parses nested groups, assigns default weights, and skips invalid entries", async () => {
state.servicesYaml = [
{
Main: [
{
Child: [{ SvcA: { icon: "a" } }, { SvcB: { icon: "b", weight: 5 } }],
},
{ SvcRoot: { icon: "r" } },
{ BadSvc: null },
],
},
];
const mod = await import("./service-helpers");
const groups = await mod.servicesFromConfig();
expect(groups).toHaveLength(1);
expect(groups[0].name).toBe("Main");
expect(groups[0].type).toBe("group");
// Root services live on the group; child groups are nested.
expect(groups[0].services.map((s) => ({ name: s.name, weight: s.weight }))).toEqual([
{ name: "SvcRoot", weight: 100 },
]);
expect(groups[0].groups).toHaveLength(1);
expect(groups[0].groups[0].name).toBe("Child");
expect(groups[0].groups[0].services.map((s) => ({ name: s.name, weight: s.weight }))).toEqual([
{ name: "SvcA", weight: 100 },
{ name: "SvcB", weight: 5 },
]);
expect(state.logger.warn).toHaveBeenCalled();
});
it("cleanServiceGroups normalizes weights, moves widget->widgets, and parses per-widget settings", async () => {
const mod = await import("./service-helpers");
const { cleanServiceGroups } = mod;
const rawGroups = [
{
name: "Group",
services: [
{
name: "svc",
showStats: "true",
weight: "not-a-number",
widgets: [
// Invalid fields/highlight should be dropped with a log message.
{ type: "iframe", fields: "{bad}", highlight: "{bad}", src: "https://example.com" },
// Type-specific boolean parsing.
{ type: "portainer", kubernetes: "true" },
{ type: "deluge", enableLeechProgress: "true", enableLeechSize: "false" },
],
// `widget` is appended after the `widgets` array.
widget: {
type: "glances",
metric: "cpu",
chart: false,
version: "3",
refreshInterval: 1500,
pointsLimit: 10,
diskUnits: "gb",
fields: '["cpu"]',
highlight: '{"level":"warning"}',
hideErrors: true,
},
},
{
name: "svc2",
weight: {},
widget: { type: "openwrt", interfaceName: "eth0" },
},
],
groups: [],
},
];
const cleaned = cleanServiceGroups(rawGroups);
expect(cleaned).toHaveLength(1);
expect(cleaned[0].type).toBe("group");
expect(cleaned[0].services).toHaveLength(2);
const svc = cleaned[0].services[0];
expect(svc.showStats).toBe(true);
expect(svc.weight).toBe(0);
expect(svc.widgets).toHaveLength(4);
// The last widget is the appended `widget` entry; it should carry service metadata.
const glancesWidget = svc.widgets[3];
expect(glancesWidget.type).toBe("glances");
expect(glancesWidget.service_group).toBe("Group");
expect(glancesWidget.service_name).toBe("svc");
expect(glancesWidget.index).toBe(3);
expect(glancesWidget.hide_errors).toBe(true);
expect(glancesWidget.fields).toEqual(["cpu"]);
expect(glancesWidget.highlight).toEqual({ level: "warning" });
expect(glancesWidget.chart).toBe(false);
expect(glancesWidget.version).toBe(3);
// Type-specific parsing for other widgets.
expect(svc.widgets[1].kubernetes).toBe(true);
expect(svc.widgets[2].enableLeechProgress).toBe(true);
expect(svc.widgets[2].enableLeechSize).toBe(false);
const svc2 = cleaned[0].services[1];
expect(svc2.weight).toBe(0);
expect(svc2.widgets).toHaveLength(1);
expect(svc2.widgets[0]).toEqual(
expect.objectContaining({
type: "openwrt",
interfaceName: "eth0",
service_group: "Group",
service_name: "svc2",
index: 0,
}),
);
expect(state.logger.error).toHaveBeenCalled();
});
it("findGroupByName deep-searches and annotates parent", async () => {
const mod = await import("./service-helpers");
const { findGroupByName } = mod;

View File

@@ -1,6 +1,14 @@
import { describe, expect, it } from "vitest";
import { asJson, formatApiCall, formatProxyUrl, getURLSearchParams, sanitizeErrorURL } from "./api-helpers";
import {
asJson,
formatApiCall,
formatProxyUrl,
getURLSearchParams,
jsonArrayFilter,
jsonArrayTransform,
sanitizeErrorURL,
} from "./api-helpers";
describe("utils/proxy/api-helpers", () => {
it("formatApiCall replaces placeholders and trims trailing slashes for {url}", () => {
@@ -48,6 +56,18 @@ describe("utils/proxy/api-helpers", () => {
expect(asJson(null)).toBeNull();
});
it("jsonArrayTransform transforms arrays and returns non-arrays unchanged", () => {
const data = Buffer.from(JSON.stringify([{ a: 1 }, { a: 2 }]));
expect(jsonArrayTransform(data, (items) => items.map((i) => i.a))).toEqual([1, 2]);
expect(jsonArrayTransform(Buffer.from(JSON.stringify({ ok: true })), () => "nope")).toEqual({ ok: true });
});
it("jsonArrayFilter filters arrays and returns non-arrays unchanged", () => {
const data = Buffer.from(JSON.stringify([{ a: 1 }, { a: 2 }]));
expect(jsonArrayFilter(data, (item) => item.a > 1)).toEqual([{ a: 2 }]);
});
it("sanitizeErrorURL redacts sensitive query params and hash fragments", () => {
const input = "https://example.com/path?apikey=123&token=abc#access_token=xyz&other=1";
const output = sanitizeErrorURL(input);
@@ -58,4 +78,15 @@ describe("utils/proxy/api-helpers", () => {
expect(url.hash).toContain("access_token=***");
expect(url.hash).toContain("other=1");
});
it("sanitizeErrorURL only redacts known keys", () => {
const input = "https://example.com/path?api_key=123&safe=ok#auth=abc&safe_hash=1";
const output = sanitizeErrorURL(input);
const url = new URL(output);
expect(url.searchParams.get("api_key")).toBe("***");
expect(url.searchParams.get("safe")).toBe("ok");
expect(url.hash).toContain("auth=***");
expect(url.hash).toContain("safe_hash=1");
});
});