mirror of
https://github.com/gethomepage/homepage.git
synced 2026-09-27 06:21:18 -07:00
Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
263 lines
9.7 KiB
JavaScript
263 lines
9.7 KiB
JavaScript
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
const { fs, yaml, config, widgetHelpers, serviceHelpers } = vi.hoisted(() => ({
|
|
fs: {
|
|
readFile: vi.fn(),
|
|
},
|
|
yaml: {
|
|
load: vi.fn(),
|
|
},
|
|
config: {
|
|
CONF_DIR: "/conf",
|
|
getSettings: vi.fn(),
|
|
substituteEnvironmentVars: vi.fn((s) => s),
|
|
default: vi.fn(),
|
|
},
|
|
widgetHelpers: {
|
|
widgetsFromConfig: vi.fn(),
|
|
cleanWidgetGroups: vi.fn(),
|
|
},
|
|
serviceHelpers: {
|
|
servicesFromDocker: vi.fn(),
|
|
servicesFromKubernetes: vi.fn(),
|
|
servicesFromConfig: vi.fn(),
|
|
cleanServiceGroups: vi.fn((g) => g),
|
|
findGroupByName: vi.fn(),
|
|
},
|
|
}));
|
|
|
|
vi.mock("fs", () => ({
|
|
promises: fs,
|
|
}));
|
|
|
|
vi.mock("utils/config/yaml", () => ({ loadYaml: yaml.load }));
|
|
|
|
vi.mock("utils/config/config", () => config);
|
|
vi.mock("utils/config/widget-helpers", () => widgetHelpers);
|
|
vi.mock("utils/config/service-helpers", () => serviceHelpers);
|
|
|
|
import { bookmarksResponse, servicesResponse, widgetsResponse } from "./api-response";
|
|
|
|
describe("utils/config/api-response", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it("bookmarksResponse returns [] when bookmarks are missing", async () => {
|
|
fs.readFile.mockResolvedValueOnce("ignored");
|
|
yaml.load.mockReturnValueOnce(null);
|
|
|
|
const res = await bookmarksResponse();
|
|
expect(res).toEqual([]);
|
|
expect(config.getSettings).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("bookmarksResponse falls back when settings cannot be loaded", async () => {
|
|
const errSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
fs.readFile.mockResolvedValueOnce("ignored");
|
|
config.getSettings.mockRejectedValueOnce(new Error("bad settings"));
|
|
yaml.load.mockReturnValueOnce([{ A: [{ LinkA: [{ href: "a" }] }] }, { B: [{ LinkB: [{ href: "b" }] }] }]);
|
|
|
|
const res = await bookmarksResponse();
|
|
expect(res.map((g) => g.name)).toEqual(["A", "B"]);
|
|
expect(errSpy).toHaveBeenCalled();
|
|
|
|
errSpy.mockRestore();
|
|
});
|
|
|
|
it("bookmarksResponse sorts groups based on settings layout", async () => {
|
|
fs.readFile.mockResolvedValueOnce("ignored");
|
|
config.getSettings.mockResolvedValueOnce({ layout: { B: {}, A: {} } });
|
|
yaml.load.mockReturnValueOnce([{ A: [{ LinkA: [{ href: "a" }] }] }, { B: [{ LinkB: [{ href: "b" }] }] }]);
|
|
|
|
const res = await bookmarksResponse();
|
|
expect(res.map((g) => g.name)).toEqual(["B", "A"]);
|
|
});
|
|
|
|
it("bookmarksResponse appends groups not present in the layout", async () => {
|
|
fs.readFile.mockResolvedValueOnce("ignored");
|
|
config.getSettings.mockResolvedValueOnce({ layout: { A: {} } });
|
|
yaml.load.mockReturnValueOnce([{ A: [{ LinkA: [{ href: "a" }] }] }, { C: [{ LinkC: [{ href: "c" }] }] }]);
|
|
|
|
const res = await bookmarksResponse();
|
|
expect(res.map((g) => g.name)).toEqual(["A", "C"]);
|
|
});
|
|
|
|
it("widgetsResponse returns sanitized configured widgets", async () => {
|
|
widgetHelpers.widgetsFromConfig.mockResolvedValueOnce([{ type: "search", options: { url: "x" } }]);
|
|
widgetHelpers.cleanWidgetGroups.mockResolvedValueOnce([{ type: "search", options: { index: 0 } }]);
|
|
|
|
expect(await widgetsResponse()).toEqual([{ type: "search", options: { index: 0 } }]);
|
|
});
|
|
|
|
it("widgetsResponse returns [] when widgets cannot be loaded", async () => {
|
|
const errSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
widgetHelpers.widgetsFromConfig.mockRejectedValueOnce(new Error("bad widgets"));
|
|
|
|
expect(await widgetsResponse()).toEqual([]);
|
|
expect(errSpy).toHaveBeenCalled();
|
|
|
|
errSpy.mockRestore();
|
|
});
|
|
|
|
it("servicesResponse merges groups and sorts services by weight then name", async () => {
|
|
// Minimal stubs for findGroupByName used within servicesResponse.
|
|
serviceHelpers.findGroupByName.mockImplementation((groups, name) => groups.find((g) => g.name === name) ?? null);
|
|
|
|
serviceHelpers.servicesFromDocker.mockResolvedValueOnce([
|
|
{
|
|
name: "GroupA",
|
|
services: [
|
|
{ name: "b", weight: 200 },
|
|
{ name: "a", weight: 200 },
|
|
],
|
|
groups: [],
|
|
},
|
|
]);
|
|
serviceHelpers.servicesFromKubernetes.mockResolvedValueOnce([
|
|
{ name: "GroupA", services: [{ name: "c", weight: 100 }], groups: [] },
|
|
]);
|
|
serviceHelpers.servicesFromConfig.mockResolvedValueOnce([
|
|
{ name: "GroupA", services: [{ name: "d", weight: 50 }], groups: [] },
|
|
{ name: "Empty", services: [], groups: [] },
|
|
]);
|
|
|
|
config.getSettings.mockResolvedValueOnce({ layout: { GroupA: {}, GroupB: {} } });
|
|
|
|
const groups = await servicesResponse();
|
|
expect(groups.map((g) => g.name)).toEqual(["GroupA"]);
|
|
expect(groups[0].services.map((s) => s.name)).toEqual(["d", "c", "a", "b"]);
|
|
});
|
|
|
|
it("servicesResponse logs when no docker services are discovered", async () => {
|
|
const debugSpy = vi.spyOn(console, "debug").mockImplementation(() => {});
|
|
serviceHelpers.findGroupByName.mockImplementation((groups, name) => groups.find((g) => g.name === name) ?? null);
|
|
|
|
serviceHelpers.servicesFromDocker.mockResolvedValueOnce([]);
|
|
serviceHelpers.servicesFromKubernetes.mockResolvedValueOnce([]);
|
|
serviceHelpers.servicesFromConfig.mockResolvedValueOnce([]);
|
|
config.getSettings.mockResolvedValueOnce({});
|
|
|
|
const groups = await servicesResponse();
|
|
|
|
expect(groups).toEqual([]);
|
|
expect(debugSpy).toHaveBeenCalledWith("No containers were found with homepage labels.");
|
|
|
|
debugSpy.mockRestore();
|
|
});
|
|
|
|
it("servicesResponse tolerates discovery/load failures and returns [] when nothing can be loaded", async () => {
|
|
const errSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
serviceHelpers.servicesFromDocker.mockRejectedValueOnce(new Error("docker bad"));
|
|
serviceHelpers.servicesFromKubernetes.mockRejectedValueOnce(new Error("kube bad"));
|
|
serviceHelpers.servicesFromConfig.mockRejectedValueOnce(new Error("config bad"));
|
|
config.getSettings.mockRejectedValueOnce(new Error("settings bad"));
|
|
|
|
const groups = await servicesResponse();
|
|
expect(groups).toEqual([]);
|
|
expect(errSpy).toHaveBeenCalled();
|
|
|
|
errSpy.mockRestore();
|
|
});
|
|
|
|
it("servicesResponse supports multi-level nested layout groups and ensures the top-level parent exists", async () => {
|
|
serviceHelpers.findGroupByName.mockImplementation(function find(groups, name, parent) {
|
|
for (const group of groups ?? []) {
|
|
if (group.name === name) {
|
|
if (parent) group.parent = parent;
|
|
return group;
|
|
}
|
|
const found = find(group.groups, name, group.name);
|
|
if (found) return found;
|
|
}
|
|
return null;
|
|
});
|
|
|
|
serviceHelpers.servicesFromDocker.mockResolvedValueOnce([
|
|
{ name: "Child", services: [{ name: "svc", weight: 1 }], groups: [] },
|
|
]);
|
|
serviceHelpers.servicesFromKubernetes.mockResolvedValueOnce([]);
|
|
serviceHelpers.servicesFromConfig.mockResolvedValueOnce([{ name: "Root", services: [], groups: [] }]);
|
|
|
|
config.getSettings.mockResolvedValueOnce({ layout: { Root: { Top: { Child: {} } } } });
|
|
|
|
const groups = await servicesResponse();
|
|
|
|
expect(groups.map((g) => g.name)).toEqual(["Root"]);
|
|
expect(groups[0].groups[0].name).toBe("Top");
|
|
expect(groups[0].groups[0].groups[0].name).toBe("Child");
|
|
expect(groups[0].groups[0].groups[0].services).toEqual([{ name: "svc", weight: 1 }]);
|
|
});
|
|
|
|
it("servicesResponse merges discovered nested groups into their configured parent layout group", async () => {
|
|
serviceHelpers.findGroupByName.mockImplementation(function find(groups, name, parent) {
|
|
for (const group of groups ?? []) {
|
|
if (group.name === name) {
|
|
if (parent) group.parent = parent;
|
|
return group;
|
|
}
|
|
const found = find(group.groups, name, group.name);
|
|
if (found) return found;
|
|
}
|
|
return null;
|
|
});
|
|
|
|
serviceHelpers.servicesFromDocker.mockResolvedValueOnce([
|
|
{
|
|
name: "Child",
|
|
services: [
|
|
{ name: "svcB", weight: 50 },
|
|
{ name: "svcA", weight: 10 },
|
|
],
|
|
groups: [],
|
|
},
|
|
]);
|
|
serviceHelpers.servicesFromKubernetes.mockResolvedValueOnce([]);
|
|
serviceHelpers.servicesFromConfig.mockResolvedValueOnce([
|
|
{
|
|
name: "Top",
|
|
services: [],
|
|
groups: [{ name: "Child", services: [], groups: [] }],
|
|
},
|
|
]);
|
|
|
|
config.getSettings.mockResolvedValueOnce({ layout: { Top: { Child: {} } } });
|
|
|
|
const groups = await servicesResponse();
|
|
|
|
expect(groups.map((g) => g.name)).toEqual(["Top"]);
|
|
expect(groups[0].groups).toHaveLength(1);
|
|
expect(groups[0].groups[0].name).toBe("Child");
|
|
expect(groups[0].groups[0].services.map((s) => s.name)).toEqual(["svcA", "svcB"]);
|
|
});
|
|
|
|
it("servicesResponse merges nested discovered groups into their configured parent when no layout is defined", async () => {
|
|
serviceHelpers.findGroupByName.mockImplementation(function find(groups, name, parent) {
|
|
for (const group of groups ?? []) {
|
|
if (group.name === name) {
|
|
if (parent) group.parent = parent;
|
|
return group;
|
|
}
|
|
const found = find(group.groups, name, group.name);
|
|
if (found) return found;
|
|
}
|
|
return null;
|
|
});
|
|
|
|
serviceHelpers.servicesFromDocker.mockResolvedValueOnce([
|
|
{ name: "Child", services: [{ name: "svc", weight: 1 }], groups: [] },
|
|
]);
|
|
serviceHelpers.servicesFromKubernetes.mockResolvedValueOnce([]);
|
|
serviceHelpers.servicesFromConfig.mockResolvedValueOnce([
|
|
{ name: "Top", services: [], groups: [{ name: "Child", services: [], groups: [] }] },
|
|
]);
|
|
config.getSettings.mockResolvedValueOnce({});
|
|
|
|
const groups = await servicesResponse();
|
|
|
|
expect(groups.map((g) => g.name)).toEqual(["Top"]);
|
|
expect(groups[0].groups[0].name).toBe("Child");
|
|
expect(groups[0].groups[0].services).toEqual([{ name: "svc", weight: 1 }]);
|
|
});
|
|
});
|