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, DashboardHostAssetError, loadDashboardHostAssets, } from "../src/dashboard-host-assets.js"; 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 = '
'; const script = "export const dashboardHost = true;\n"; readFileMock.mockResolvedValueOnce(html).mockResolvedValueOnce(script); const assets = await loadDashboardHostAssets(); expect(readFileMock.mock.calls).toEqual([ [new URL("file:///surface/browser/index.html"), "utf8"], [new URL("file:///surface/browser/assets/ekklesia-dashboard/app.js"), "utf8"], ]); expect(assets).toEqual({ html, htmlEtag: sha256Etag(html), 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().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("") .mockRejectedValueOnce(new Error("ENOENT app.js")); await expect(loadDashboardHostAssets()).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); } }); });