fix(custom-widgets): harden v2 authoring

This commit is contained in:
Thomas Camlong committed 2026-07-20 22:16:18 +02:00
1 parent ee832ad3c6
commit 7c66c912e7
171 files changed
+20311 -50527

No files matched your search

+49 -1
View File
@@ -2,11 +2,59 @@ import { describe, expect, test } from "vitest";
import { CUSTOM_WIDGET_STARTER } from "@homarr/custom-widgets/core";
import { validateWorkshopWidget } from "./schema";
import { validateWorkshopWidget, workshopSubmissionInputSchema } from "./schema";
describe("Workshop widget validation", () => {
test("accepts a canonical credential-free widget", () => {
expect(validateWorkshopWidget(JSON.stringify(CUSTOM_WIDGET_STARTER)).success).toBe(true);
expect(
workshopSubmissionInputSchema.safeParse({
title: "Starter widget",
description: "",
content: JSON.stringify(CUSTOM_WIDGET_STARTER),
}).success,
).toBe(true);
});
test("uses canonical option-binding validation before publication", () => {
const request = {
id: "data",
sourceId: "default",
kind: "query" as const,
method: "GET" as const,
pathTemplate: "/data/{endpointId}",
parameters: { endpointId: "string" as const, limit: "number" as const },
optionsBinding: { endpointId: { $option: "endpointId" }, limit: 5 },
queryTemplate: { limit: { $param: "limit" } },
auth: "inherit" as const,
minimumBoardPermission: "view" as const,
trigger: "load" as const,
};
const widget = {
...CUSTOM_WIDGET_STARTER,
requests: [request],
optionsSchema: {
type: "object" as const,
properties: { endpointId: { type: "string" as const } },
required: ["endpointId"],
additionalProperties: false as const,
},
defaultOptions: { endpointId: "local" },
};
expect(validateWorkshopWidget(JSON.stringify(widget)).success).toBe(true);
expect(
validateWorkshopWidget(JSON.stringify({ ...widget, requests: [{ ...request, optionsBinding: { limit: 5 } }] }))
.success,
).toBe(false);
expect(
validateWorkshopWidget(
JSON.stringify({
...widget,
requests: [{ ...request, optionsBinding: { endpointId: { $option: "missing" }, limit: 5 } }],
}),
).success,
).toBe(false);
});
test("rejects literal credentials in templates and icon URLs", () => {