fix: add browser error passthrough regression coverage (#26380) (thanks @TarasShyn)

This commit is contained in:
Peter Steinberger
2026-02-26 20:55:23 +01:00
parent e555bfb1d8
commit 425a7a99c4
2 changed files with 42 additions and 1 deletions

View File

@@ -62,6 +62,7 @@ Docs: https://docs.openclaw.ai
- Browser/Extension relay shutdown: flush pending extension-request timers/rejections during relay `stop()` before socket/server teardown so in-flight extension waits do not survive shutdown windows. Landed from contributor PR #24142 by @kevinWangSheng.
- Browser/Extension relay reconnect resilience: keep CDP clients alive across brief MV3 extension disconnect windows, wait briefly for extension reconnect before failing in-flight CDP commands, and only tear down relay target/client state after reconnect grace expires. Landed from contributor PR #27617 by @davidemanuelDEV.
- Browser/Route decode hardening: guard malformed percent-encoding in relay target action routes and browser route-param decoding so crafted `%` paths return `400` instead of crashing/unhandled URI decode failures. Landed from contributor PR #11880 by @Yida-Dev.
- Browser/Error visibility: preserve browser-control application error messages (HTTP 4xx/5xx) instead of rewriting them as generic reachability failures. Landed from contributor PR #26380 by @TarasShyn.
- Feishu/Permission error dispatch: merge sender-name permission notices into the main inbound dispatch so one user message produces one agent turn/reply (instead of a duplicate permission-notice turn), with regression coverage. (#27381) thanks @byungsker.
- Feishu/Inbound message metadata: include inbound `message_id` in `BodyForAgent` on a dedicated metadata line so agents can reliably correlate and act on media/message operations that require message IDs, with regression coverage. (#27253) thanks @xss925175263.
- Feishu/Doc tools: route `feishu_doc` and `feishu_app_scopes` through the active agent account context (with explicit `accountId` override support) so multi-account agents no longer default to the first configured app, with regression coverage for context routing and explicit override behavior. (#27338) thanks @AaronL725.

View File

@@ -8,6 +8,10 @@ const mocks = vi.hoisted(() => ({
},
},
})),
dispatch: vi.fn<() => Promise<{ status: number; body: unknown }>>(async () => ({
status: 200,
body: { ok: true },
})),
}));
vi.mock("../config/config.js", async (importOriginal) => {
@@ -25,7 +29,7 @@ vi.mock("./control-service.js", () => ({
vi.mock("./routes/dispatcher.js", () => ({
createBrowserRouteDispatcher: vi.fn(() => ({
dispatch: vi.fn(async () => ({ status: 200, body: { ok: true } })),
dispatch: mocks.dispatch,
})),
}));
@@ -47,6 +51,8 @@ describe("fetchBrowserJson loopback auth", () => {
beforeEach(() => {
vi.restoreAllMocks();
mocks.loadConfig.mockClear();
mocks.dispatch.mockClear();
mocks.dispatch.mockResolvedValue({ status: 200, body: { ok: true } });
mocks.loadConfig.mockReturnValue({
gateway: {
auth: {
@@ -60,6 +66,15 @@ describe("fetchBrowserJson loopback auth", () => {
vi.unstubAllGlobals();
});
async function expectServiceError(promise: Promise<unknown>, expectedMessage: string) {
await expect(promise).rejects.toThrow(expectedMessage);
try {
await promise;
} catch (error) {
expect(String(error)).not.toContain("Can't reach the OpenClaw browser control service");
}
}
it("adds bearer auth for loopback absolute HTTP URLs", async () => {
const fetchMock = stubJsonFetchOk();
@@ -114,4 +129,29 @@ describe("fetchBrowserJson loopback auth", () => {
const headers = new Headers(init?.headers);
expect(headers.get("authorization")).toBe("Bearer loopback-token");
});
it("keeps absolute HTTP service errors unwrapped", async () => {
const fetchMock = vi.fn<(input: RequestInfo | URL, init?: RequestInit) => Promise<Response>>(
async () =>
new Response("browser route failed", {
status: 502,
headers: { "Content-Type": "text/plain" },
}),
);
vi.stubGlobal("fetch", fetchMock);
await expectServiceError(
fetchBrowserJson<{ ok: boolean }>("http://example.com/"),
"browser route failed",
);
});
it("keeps local dispatcher service errors unwrapped", async () => {
mocks.dispatch.mockResolvedValueOnce({
status: 500,
body: { error: "target unavailable" },
});
await expectServiceError(fetchBrowserJson<{ ok: boolean }>("/json/list"), "target unavailable");
});
});