import fs from "node:fs/promises"; import type { IncomingMessage } from "node:http"; import os from "node:os"; import path from "node:path"; import { afterEach, describe, expect, it, vi } from "vitest"; const { resolveControlUiRootSyncMock, isPackageProvenControlUiRootSyncMock } = vi.hoisted(() => ({ resolveControlUiRootSyncMock: vi.fn(), isPackageProvenControlUiRootSyncMock: vi.fn().mockReturnValue(true), })); vi.mock("../infra/control-ui-assets.js", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, resolveControlUiRootSync: resolveControlUiRootSyncMock, isPackageProvenControlUiRootSync: isPackageProvenControlUiRootSyncMock, }; }); const { handleControlUiHttpRequest } = await import("./control-ui.js"); const { makeMockHttpResponse } = await import("./test-http-response.js"); async function withControlUiRoot(fn: (tmp: string) => Promise) { const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-ui-auto-root-")); try { await fs.writeFile(path.join(tmp, "index.html"), "fallback\n"); return await fn(tmp); } finally { await fs.rm(tmp, { recursive: true, force: true }); } } afterEach(() => { resolveControlUiRootSyncMock.mockReset(); isPackageProvenControlUiRootSyncMock.mockReset(); isPackageProvenControlUiRootSyncMock.mockReturnValue(true); }); describe("handleControlUiHttpRequest auto-detected root", () => { it("serves hardlinked asset files for bundled auto-detected roots", async () => { await withControlUiRoot(async (tmp) => { const assetsDir = path.join(tmp, "assets"); await fs.mkdir(assetsDir, { recursive: true }); await fs.writeFile(path.join(assetsDir, "app.js"), "console.log('hi');"); await fs.link(path.join(assetsDir, "app.js"), path.join(assetsDir, "app.hl.js")); resolveControlUiRootSyncMock.mockReturnValue(tmp); const { res, end } = makeMockHttpResponse(); const handled = handleControlUiHttpRequest( { url: "/assets/app.hl.js", method: "GET" } as IncomingMessage, res, ); expect(handled).toBe(true); expect(res.statusCode).toBe(200); expect(String(end.mock.calls[0]?.[0] ?? "")).toBe("console.log('hi');"); }); }); it("serves hardlinked SPA fallback index.html for bundled auto-detected roots", async () => { await withControlUiRoot(async (tmp) => { const sourceIndex = path.join(tmp, "index.source.html"); const indexPath = path.join(tmp, "index.html"); await fs.writeFile(sourceIndex, "fallback-hardlink\n"); await fs.rm(indexPath); await fs.link(sourceIndex, indexPath); resolveControlUiRootSyncMock.mockReturnValue(tmp); const { res, end } = makeMockHttpResponse(); const handled = handleControlUiHttpRequest( { url: "/dashboard", method: "GET" } as IncomingMessage, res, ); expect(handled).toBe(true); expect(res.statusCode).toBe(200); expect(String(end.mock.calls[0]?.[0] ?? "")).toBe("fallback-hardlink\n"); }); }); it("rejects hardlinked assets for non-package-proven auto-detected roots", async () => { isPackageProvenControlUiRootSyncMock.mockReturnValue(false); await withControlUiRoot(async (tmp) => { const assetsDir = path.join(tmp, "assets"); await fs.mkdir(assetsDir, { recursive: true }); await fs.writeFile(path.join(assetsDir, "app.js"), "console.log('hi');"); await fs.link(path.join(assetsDir, "app.js"), path.join(assetsDir, "app.hl.js")); resolveControlUiRootSyncMock.mockReturnValue(tmp); const { res } = makeMockHttpResponse(); const handled = handleControlUiHttpRequest( { url: "/assets/app.hl.js", method: "GET" } as IncomingMessage, res, ); expect(handled).toBe(true); expect(res.statusCode).toBe(404); }); }); });