import { createHash } from "node:crypto"; import { beforeEach, describe, expect, it, vi } from "vitest"; const readFileMock = vi.hoisted(() => vi.fn()); vi.mock("node:fs/promises", () => ({ readFile: readFileMock })); vi.mock("@ekklesia/shell/dashboard-assets", () => ({ dashboardHostAssetUrls: Object.freeze({ html: "file:///surface/browser/index.html", script: "file:///surface/browser/assets/ekklesia-dashboard/app.js", }), })); import { createDashboardHostAssets, DASHBOARD_API_ORIGIN_PLACEHOLDER, DashboardHostAssetError, loadDashboardHostAssets, renderDashboardPage, } from "../src/dashboard-host-assets.js"; const API_ORIGIN = "https://api.example.test"; function sha256Etag(text: string): string { return `"${createHash("sha256").update(text).digest("base64url")}"`; } describe("Dashboard host asset loading", () => { beforeEach(() => readFileMock.mockReset()); it("reads only the two declared file URLs in order and freezes one consistent snapshot", async () => { const html = `${DASHBOARD_API_ORIGIN_PLACEHOLDER}
`; const script = "export const dashboardHost = true;\n"; readFileMock.mockResolvedValueOnce(html).mockResolvedValueOnce(script); const assets = await loadDashboardHostAssets(API_ORIGIN); expect(readFileMock.mock.calls).toEqual([ [new URL("file:///surface/browser/index.html"), "utf8"], [new URL("file:///surface/browser/assets/ekklesia-dashboard/app.js"), "utf8"], ]); const rendered = `
`; expect(assets).toEqual({ html: rendered, htmlEtag: sha256Etag(rendered), script, scriptEtag: sha256Etag(script), }); expect(Object.isFrozen(assets)).toBe(true); }); it("fails deterministically without attempting a neighboring file or exposing the read error", async () => { readFileMock.mockRejectedValueOnce( new Error("ENOENT /home/operator/private/surface-build secret-token"), ); const failure = await loadDashboardHostAssets(API_ORIGIN).catch((error: unknown) => error); expect(readFileMock).toHaveBeenCalledTimes(1); expect(failure).toBeInstanceOf(DashboardHostAssetError); expect(failure).toMatchObject({ code: "DASHBOARD_HOST_ASSET_UNAVAILABLE", message: "The Dashboard host browser assets are unavailable.", name: "DashboardHostAssetError", }); expect((failure as Error).stack).toBe( "DashboardHostAssetError: The Dashboard host browser assets are unavailable.", ); const publicText = `${String(failure)}\n${JSON.stringify(failure)}\n${(failure as Error).stack}`; for (const secret of ["/home/", "operator", "ENOENT", "secret-token"]) { expect(publicText).not.toContain(secret); } }); it("fails when the script is missing even though the page was read", async () => { readFileMock .mockResolvedValueOnce(`${DASHBOARD_API_ORIGIN_PLACEHOLDER}`) .mockRejectedValueOnce(new Error("ENOENT app.js")); await expect(loadDashboardHostAssets(API_ORIGIN)).rejects.toBeInstanceOf( DashboardHostAssetError, ); expect(readFileMock).toHaveBeenCalledTimes(2); }); it("rejects an empty page or script as an unavailable shell build", () => { for (const [html, script] of [ ["", "export {};"], ["", " \n"], ] as const) { expect(() => createDashboardHostAssets(html, script)).toThrow(DashboardHostAssetError); } }); it("fails startup on a page without exactly one empty API-origin element (ADR 0045)", async () => { for (const html of [ "", `${DASHBOARD_API_ORIGIN_PLACEHOLDER}${DASHBOARD_API_ORIGIN_PLACEHOLDER}`, '', ]) { readFileMock.mockReset(); readFileMock.mockResolvedValueOnce(html).mockResolvedValueOnce("export {};\n"); await expect(loadDashboardHostAssets(API_ORIGIN)).rejects.toBeInstanceOf( DashboardHostAssetError, ); } }); }); describe("Dashboard page API origin (ADR 0045)", () => { const page = `${DASHBOARD_API_ORIGIN_PLACEHOLDER}`; it("writes the API's origin into the one placeholder, in either topology", () => { expect(renderDashboardPage(page, "https://api.example.test")).toBe( '', ); expect(renderDashboardPage(page, "http://127.0.0.1:3000")).toBe( '', ); }); it.each([ "https://api.example.test/", "https://api.example.test/api", 'https://api.example.test">