fix: test browser.request profile body fallback (#28852) (thanks @Sid-Qin)

This commit is contained in:
Peter Steinberger
2026-03-02 06:24:16 +00:00
parent fa875a6bf7
commit 0eebae44f6
2 changed files with 104 additions and 0 deletions

View File

@@ -39,6 +39,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- Security/Prompt spoofing hardening: stop injecting queued runtime events into user-role prompt text, route them through trusted system-prompt context, and neutralize inbound spoof markers like `[System Message]` and line-leading `System:` in untrusted message content. (#30448)
- Gateway/Node browser proxy routing: honor `profile` from `browser.request` JSON body when query params omit it, while preserving query-profile precedence when both are present. (#28852) Thanks @Sid-Qin.
- Browser/Remote CDP ownership checks: skip local-process ownership errors for non-loopback remote CDP profiles when HTTP is reachable but the websocket handshake fails, and surface the remote websocket attach/retry path instead. (#15582) Landed from contributor (#28780) Thanks @stubbi, @bsormagec, @unblockedgamesstudio and @vincentkoc.
- Docker/Image health checks: add Dockerfile `HEALTHCHECK` that probes gateway `GET /healthz` so container runtimes can mark unhealthy instances without requiring auth credentials in the probe command. (#11478) Thanks @U-C4N and @vincentkoc.
- Daemon/systemd checks in containers: treat missing `systemctl` invocations (including `spawn systemctl ENOENT`/`EACCES`) as unavailable service state during `is-enabled` checks, preventing container flows from failing with `Gateway service check failed` before install/status handling can continue. (#26089) Thanks @sahilsatralkar and @vincentkoc.

View File

@@ -0,0 +1,103 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
const { loadConfigMock, isNodeCommandAllowedMock, resolveNodeCommandAllowlistMock } = vi.hoisted(
() => ({
loadConfigMock: vi.fn(),
isNodeCommandAllowedMock: vi.fn(),
resolveNodeCommandAllowlistMock: vi.fn(),
}),
);
vi.mock("../../config/config.js", () => ({
loadConfig: loadConfigMock,
}));
vi.mock("../node-command-policy.js", () => ({
isNodeCommandAllowed: isNodeCommandAllowedMock,
resolveNodeCommandAllowlist: resolveNodeCommandAllowlistMock,
}));
import { browserHandlers } from "./browser.js";
type RespondCall = [boolean, unknown?, { code: number; message: string }?];
function createContext() {
const invoke = vi.fn(async () => ({
ok: true,
payload: {
result: { ok: true },
},
}));
const listConnected = vi.fn(() => [
{
nodeId: "node-1",
caps: ["browser"],
commands: ["browser.proxy"],
platform: "linux",
},
]);
return {
invoke,
listConnected,
};
}
async function runBrowserRequest(params: Record<string, unknown>) {
const respond = vi.fn();
const nodeRegistry = createContext();
await browserHandlers["browser.request"]({
params,
respond: respond as never,
context: { nodeRegistry } as never,
client: null,
req: { type: "req", id: "req-1", method: "browser.request" },
isWebchatConnect: () => false,
});
return { respond, nodeRegistry };
}
describe("browser.request profile selection", () => {
beforeEach(() => {
loadConfigMock.mockReturnValue({
gateway: { nodes: { browser: { mode: "auto" } } },
});
resolveNodeCommandAllowlistMock.mockReturnValue([]);
isNodeCommandAllowedMock.mockReturnValue({ ok: true });
});
it("uses profile from request body when query profile is missing", async () => {
const { respond, nodeRegistry } = await runBrowserRequest({
method: "POST",
path: "/act",
body: { profile: "work", request: { action: "click", ref: "btn1" } },
});
expect(nodeRegistry.invoke).toHaveBeenCalledWith(
expect.objectContaining({
command: "browser.proxy",
params: expect.objectContaining({
profile: "work",
}),
}),
);
const call = respond.mock.calls[0] as RespondCall | undefined;
expect(call?.[0]).toBe(true);
});
it("prefers query profile over body profile when both are present", async () => {
const { nodeRegistry } = await runBrowserRequest({
method: "POST",
path: "/act",
query: { profile: "chrome" },
body: { profile: "work", request: { action: "click", ref: "btn1" } },
});
expect(nodeRegistry.invoke).toHaveBeenCalledWith(
expect.objectContaining({
params: expect.objectContaining({
profile: "chrome",
}),
}),
);
});
});