test(gateway): dedupe control-ui fixture setup and cover query asset 404

This commit is contained in:
Peter Steinberger
2026-02-21 19:30:25 +00:00
parent 8f1b467646
commit 3274a1b804

View File

@@ -46,6 +46,22 @@ vi.mock("ws", () => ({
})); }));
describe("GatewayClient", () => { describe("GatewayClient", () => {
async function withControlUiRoot(
params: { faviconSvg?: string; indexHtml?: string },
run: (tmp: string) => Promise<void>,
) {
const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-ui-"));
try {
await fs.writeFile(path.join(tmp, "index.html"), params.indexHtml ?? "<html></html>\n");
if (typeof params.faviconSvg === "string") {
await fs.writeFile(path.join(tmp, "favicon.svg"), params.faviconSvg);
}
await run(tmp);
} finally {
await fs.rm(tmp, { recursive: true, force: true });
}
}
test("uses a large maxPayload for node snapshots", () => { test("uses a large maxPayload for node snapshots", () => {
wsMockState.last = null; wsMockState.last = null;
const client = new GatewayClient({ url: "ws://127.0.0.1:1" }); const client = new GatewayClient({ url: "ws://127.0.0.1:1" });
@@ -57,10 +73,7 @@ describe("GatewayClient", () => {
}); });
it("returns 404 for missing static asset paths instead of SPA fallback", async () => { it("returns 404 for missing static asset paths instead of SPA fallback", async () => {
const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-ui-")); await withControlUiRoot({ faviconSvg: "<svg/>" }, async (tmp) => {
try {
await fs.writeFile(path.join(tmp, "index.html"), "<html></html>\n");
await fs.writeFile(path.join(tmp, "favicon.svg"), "<svg/>");
const { res } = makeControlUiResponse(); const { res } = makeControlUiResponse();
const handled = handleControlUiHttpRequest( const handled = handleControlUiHttpRequest(
{ url: "/webchat/favicon.svg", method: "GET" } as IncomingMessage, { url: "/webchat/favicon.svg", method: "GET" } as IncomingMessage,
@@ -69,15 +82,24 @@ describe("GatewayClient", () => {
); );
expect(handled).toBe(true); expect(handled).toBe(true);
expect(res.statusCode).toBe(404); expect(res.statusCode).toBe(404);
} finally { });
await fs.rm(tmp, { recursive: true, force: true }); });
}
it("returns 404 for missing static assets with query strings", async () => {
await withControlUiRoot({}, async (tmp) => {
const { res } = makeControlUiResponse();
const handled = handleControlUiHttpRequest(
{ url: "/webchat/favicon.svg?v=1", method: "GET" } as IncomingMessage,
res,
{ root: { kind: "resolved", path: tmp } },
);
expect(handled).toBe(true);
expect(res.statusCode).toBe(404);
});
}); });
it("still serves SPA fallback for extensionless paths", async () => { it("still serves SPA fallback for extensionless paths", async () => {
const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-ui-")); await withControlUiRoot({}, async (tmp) => {
try {
await fs.writeFile(path.join(tmp, "index.html"), "<html></html>\n");
const { res } = makeControlUiResponse(); const { res } = makeControlUiResponse();
const handled = handleControlUiHttpRequest( const handled = handleControlUiHttpRequest(
{ url: "/webchat/chat", method: "GET" } as IncomingMessage, { url: "/webchat/chat", method: "GET" } as IncomingMessage,
@@ -86,15 +108,11 @@ describe("GatewayClient", () => {
); );
expect(handled).toBe(true); expect(handled).toBe(true);
expect(res.statusCode).toBe(200); expect(res.statusCode).toBe(200);
} finally { });
await fs.rm(tmp, { recursive: true, force: true });
}
}); });
it("HEAD returns 404 for missing static assets consistent with GET", async () => { it("HEAD returns 404 for missing static assets consistent with GET", async () => {
const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-ui-")); await withControlUiRoot({}, async (tmp) => {
try {
await fs.writeFile(path.join(tmp, "index.html"), "<html></html>\n");
const { res } = makeControlUiResponse(); const { res } = makeControlUiResponse();
const handled = handleControlUiHttpRequest( const handled = handleControlUiHttpRequest(
{ url: "/webchat/favicon.svg", method: "HEAD" } as IncomingMessage, { url: "/webchat/favicon.svg", method: "HEAD" } as IncomingMessage,
@@ -103,15 +121,11 @@ describe("GatewayClient", () => {
); );
expect(handled).toBe(true); expect(handled).toBe(true);
expect(res.statusCode).toBe(404); expect(res.statusCode).toBe(404);
} finally { });
await fs.rm(tmp, { recursive: true, force: true });
}
}); });
it("serves SPA fallback for dotted path segments that are not static assets", async () => { it("serves SPA fallback for dotted path segments that are not static assets", async () => {
const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-ui-")); await withControlUiRoot({}, async (tmp) => {
try {
await fs.writeFile(path.join(tmp, "index.html"), "<html></html>\n");
for (const route of ["/webchat/user/jane.doe", "/webchat/v2.0", "/settings/v1.2"]) { for (const route of ["/webchat/user/jane.doe", "/webchat/v2.0", "/settings/v1.2"]) {
const { res } = makeControlUiResponse(); const { res } = makeControlUiResponse();
const handled = handleControlUiHttpRequest( const handled = handleControlUiHttpRequest(
@@ -122,15 +136,11 @@ describe("GatewayClient", () => {
expect(handled).toBe(true); expect(handled).toBe(true);
expect(res.statusCode, `expected 200 for ${route}`).toBe(200); expect(res.statusCode, `expected 200 for ${route}`).toBe(200);
} }
} finally { });
await fs.rm(tmp, { recursive: true, force: true });
}
}); });
it("serves SPA fallback for .html paths that do not exist on disk", async () => { it("serves SPA fallback for .html paths that do not exist on disk", async () => {
const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-ui-")); await withControlUiRoot({}, async (tmp) => {
try {
await fs.writeFile(path.join(tmp, "index.html"), "<html></html>\n");
const { res } = makeControlUiResponse(); const { res } = makeControlUiResponse();
const handled = handleControlUiHttpRequest( const handled = handleControlUiHttpRequest(
{ url: "/webchat/foo.html", method: "GET" } as IncomingMessage, { url: "/webchat/foo.html", method: "GET" } as IncomingMessage,
@@ -139,9 +149,7 @@ describe("GatewayClient", () => {
); );
expect(handled).toBe(true); expect(handled).toBe(true);
expect(res.statusCode).toBe(200); expect(res.statusCode).toBe(200);
} finally { });
await fs.rm(tmp, { recursive: true, force: true });
}
}); });
}); });