feat: finish the homepage plugin, move service links and canvas out of core (B19)

This commit is contained in:
LukeGus committed 2026-09-24 15:07:25 -05:00
1 parent 21937a6b02
commit d4ec444558
256 files changed
+27365 -9077

No files matched your search

@@ -0,0 +1,124 @@
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { startServer, type TestServer } from "./helpers";
let server: TestServer;
afterEach(async () => {
await server.close();
});
describe("homepage item routes", () => {
beforeEach(async () => {
server = await startServer();
});
it("creates, lists, updates and deletes an item", async () => {
const created = await server.request("POST", "/items", {
body: { typeId: "clock", title: "My Clock", config: { format: "24h" } },
});
expect(created.status).toBe(201);
expect(created.body).toMatchObject({ typeId: "clock", title: "My Clock" });
expect(created.body.syncId).toEqual(expect.any(String));
const list = await server.request("GET", "/items");
expect(list.body).toHaveLength(1);
const updated = await server.request("PUT", `/items/${created.body.id}`, {
body: { title: "Renamed" },
});
expect(updated.status).toBe(200);
expect(updated.body.title).toBe("Renamed");
const deleted = await server.request("DELETE", `/items/${created.body.id}`);
expect(deleted.status).toBe(200);
expect((await server.request("GET", "/items")).body).toHaveLength(0);
});
it("rejects a missing typeId", async () => {
const res = await server.request("POST", "/items", { body: {} });
expect(res.status).toBe(400);
});
it("404s updating or deleting another user's item", async () => {
const created = await server.request("POST", "/items", {
body: { typeId: "clock" },
});
const res = await server.request("DELETE", `/items/${created.body.id}`, {
user: "user-2",
});
expect(res.status).toBe(404);
});
});
describe("homepage layout routes", () => {
beforeEach(async () => {
server = await startServer();
});
it("returns null before any layout is saved, then round-trips one", async () => {
expect((await server.request("GET", "/layout")).body).toBeNull();
const saved = await server.request("PUT", "/layout", {
body: { entries: [], pan: { x: 0, y: 0 }, zoom: 1 },
});
expect(saved.status).toBe(200);
expect(saved.body.layout).toEqual({
entries: [],
pan: { x: 0, y: 0 },
zoom: 1,
});
const fetched = await server.request("GET", "/layout");
expect(fetched.body.layout.zoom).toBe(1);
});
});
describe("service link routes", () => {
beforeEach(async () => {
server = await startServer();
});
it("creates, lists, updates and deletes a service link", async () => {
const created = await server.request("POST", "/service-links", {
body: { label: "Grafana", url: "grafana.local" },
});
expect(created.status).toBe(201);
// A bare host gets normalized to http://.
expect(created.body.url).toBe("http://grafana.local");
const list = await server.request("GET", "/service-links");
expect(list.body).toHaveLength(1);
const updated = await server.request(
"PUT",
`/service-links/${created.body.id}`,
{ body: { label: "Grafana Dashboard" } },
);
expect(updated.body.label).toBe("Grafana Dashboard");
const deleted = await server.request(
"DELETE",
`/service-links/${created.body.id}`,
);
expect(deleted.status).toBe(200);
expect((await server.request("GET", "/service-links")).body).toHaveLength(
0,
);
});
it("rejects an invalid url", async () => {
const res = await server.request("POST", "/service-links", {
body: { label: "Bad", url: "javascript:alert(1)" },
});
expect(res.status).toBe(400);
});
});
describe("permission denial", () => {
it("answers 403 for every route without homepage.use", async () => {
server = await startServer({ permissions: [] });
expect((await server.request("GET", "/items")).status).toBe(403);
expect((await server.request("GET", "/layout")).status).toBe(403);
expect((await server.request("GET", "/service-links")).status).toBe(403);
});
});