import { describe, expect, test } from "vitest"; import { CUSTOM_WIDGET_STARTER } from "@homarr/custom-widgets/core"; import { githubAvatarUrl, githubProfileUrl, normalizeHttpUrl, resolveHomarrUrlConfig, validateWorkshopWidget, workshopReportCategorySchema, workshopSubmissionInputSchema, workshopSubmissionSummarySchema, } from "./schema"; describe("Workshop URL configuration", () => { test("derives GitHub identity URLs from the stored username", () => { expect(githubProfileUrl("octocat")).toBe("https://github.com/octocat"); expect(githubAvatarUrl("octocat")).toBe("https://github.com/octocat.png"); }); test("normalizes the URL contract and derives Workshop defaults", () => { expect( resolveHomarrUrlConfig({ homarrWebsiteUrl: "https://docs.example.com/", workshopApiUrl: "https://api.example.com///", }), ).toEqual({ homarrWebsiteUrl: "https://docs.example.com", workshopApiUrl: "https://api.example.com", workshopWebUrl: "https://docs.example.com/workshop", }); }); test("rejects unsafe or ambiguous public URLs", () => { expect(() => normalizeHttpUrl("file:///tmp/workshop", "WORKSHOP_API_URL")).toThrow("HTTP or HTTPS"); expect(() => normalizeHttpUrl("https://user:secret@example.com", "WORKSHOP_API_URL")).toThrow("credentials"); expect(() => normalizeHttpUrl("https://example.com/?token=secret", "WORKSHOP_API_URL")).toThrow("query string"); expect(() => normalizeHttpUrl("\nhttps://example.com", "WORKSHOP_API_URL")).toThrow("control characters"); }); }); describe("Workshop widget validation", () => { test("accepts outdated as a report category", () => { expect(workshopReportCategorySchema.parse("outdated")).toBe("outdated"); }); test("accepts a canonical credential-free widget", () => { expect(validateWorkshopWidget(JSON.stringify(CUSTOM_WIDGET_STARTER)).success).toBe(true); expect( workshopSubmissionInputSchema.safeParse({ type: "customWidget", title: "Starter widget", description: "", content: JSON.stringify(CUSTOM_WIDGET_STARTER), }).success, ).toBe(true); }); test("normalizes widget submissions to readable multi-line JSON", () => { const parsed = workshopSubmissionInputSchema.parse({ type: "customWidget", title: "Starter widget", description: "", content: JSON.stringify(CUSTOM_WIDGET_STARTER), }); expect(parsed.content).toContain('\n "$schema":'); expect(JSON.parse(parsed.content)).toEqual(CUSTOM_WIDGET_STARTER); }); test("accepts Custom CSS without treating it as widget JSON", () => { expect( workshopSubmissionInputSchema.safeParse({ type: "customCss", title: "Quiet dashboard", description: "", content: ".mantine-Card-root { box-shadow: none; }", }).success, ).toBe(true); }); test("validates direct bindings in the official client before publication", () => { const widget = { ...CUSTOM_WIDGET_STARTER, requests: { data: { path: "/data/{option:endpointId}", query: { limit: 5 }, }, }, options: { endpointId: { label: "Endpoint", control: "text", default: "local" } }, }; expect(validateWorkshopWidget(JSON.stringify(widget)).success).toBe(true); expect( validateWorkshopWidget(JSON.stringify({ ...widget, requests: { data: { path: "/data/{option:missing}" } } })) .success, ).toBe(false); expect( validateWorkshopWidget( JSON.stringify({ ...widget, requests: { data: { path: "/data/{param:endpointId}" } }, }), ).success, ).toBe(false); }); test("normalizes PocketBase records before rendering", () => { const result = workshopSubmissionSummarySchema.parse({ id: "jzyxskmimxn42io", title: "Tautulli Activity", description: "Shows active Plex streams from Tautulli", widgetSchema: "homarr-custom-widget-v2", screenshots: "[]", author: "wsqylmotymtqpox", revision: 1, created: "2026-07-21 14:19:25.790Z", updated: "2026-07-21 14:19:25.790Z", }); expect(result.type).toBe("customWidget"); expect(result.screenshots).toEqual([]); expect(result.authorName).toBe(""); expect(result.upvotes).toBe(0); }); test("rejects literal credentials in templates and icon URLs", () => { expect( validateWorkshopWidget( JSON.stringify({ ...CUSTOM_WIDGET_STARTER, template: "Authorization: Bearer literal-secret-value", }), ).success, ).toBe(false); expect( validateWorkshopWidget( JSON.stringify({ ...CUSTOM_WIDGET_STARTER, iconUrl: "https://example.com/icon.png?clientSecret=literal-secret", }), ).success, ).toBe(false); expect( validateWorkshopWidget( JSON.stringify({ ...CUSTOM_WIDGET_STARTER, requests: { load: { path: "/load", headers: { "X-Api-Key": "literal-secret-value" } } }, }), ).success, ).toBe(false); }); });