fix(clickclack): bound api error response bodies · openclaw/openclaw@f14a2cb
vincentkoc
·
2026-06-19
·
via Recent Commits to openclaw:main
File tree
extensions/clickclack/src
| Original file line number | Diff line number | Diff line change |
|---|
|
| 1 | +import { describe, expect, it, vi } from "vitest"; |
| 2 | +import { createClickClackClient } from "./http-client.js"; |
| 3 | + |
| 4 | +function streamedErrorResponse(body: string, limit: number) { |
| 5 | +const encoded = new TextEncoder().encode(body); |
| 6 | +let readCount = 0; |
| 7 | +const cancel = vi.fn(async () => undefined); |
| 8 | +const releaseLock = vi.fn(); |
| 9 | +const text = vi.fn(async () => { |
| 10 | +throw new Error("raw response.text() should not be used"); |
| 11 | +}); |
| 12 | + |
| 13 | +const response = { |
| 14 | +ok: false, |
| 15 | +status: 502, |
| 16 | + text, |
| 17 | +body: { |
| 18 | +getReader: () => ({ |
| 19 | +read: async () => { |
| 20 | +if (readCount > 0) { |
| 21 | +return { done: true, value: undefined }; |
| 22 | +} |
| 23 | +readCount += 1; |
| 24 | +return { done: false, value: encoded }; |
| 25 | +}, |
| 26 | + cancel, |
| 27 | + releaseLock, |
| 28 | +}), |
| 29 | +}, |
| 30 | +} as unknown as Response; |
| 31 | + |
| 32 | +return { |
| 33 | + response, |
| 34 | + cancel, |
| 35 | + releaseLock, |
| 36 | + text, |
| 37 | +expectedDetail: body.slice(0, limit), |
| 38 | +}; |
| 39 | +} |
| 40 | + |
| 41 | +describe("ClickClack HTTP client", () => { |
| 42 | +it("bounds error response bodies without using raw response.text()", async () => { |
| 43 | +const streamed = streamedErrorResponse("x".repeat(9000), 8 * 1024); |
| 44 | +const fetchMock = vi.fn(async () => streamed.response); |
| 45 | +const client = createClickClackClient({ |
| 46 | +baseUrl: "https://clickclack.example", |
| 47 | +token: "test-token", |
| 48 | +fetch: fetchMock, |
| 49 | +}); |
| 50 | + |
| 51 | +await expect(client.me()).rejects.toThrow(`ClickClack 502: ${streamed.expectedDetail}`); |
| 52 | + |
| 53 | +expect(streamed.text).not.toHaveBeenCalled(); |
| 54 | +expect(streamed.cancel).toHaveBeenCalledTimes(1); |
| 55 | +expect(streamed.releaseLock).toHaveBeenCalledTimes(1); |
| 56 | +}); |
| 57 | +}); |
| Original file line number | Diff line number | Diff line change |
|---|
@@ -2,6 +2,7 @@
|
2 | 2 | * Thin ClickClack REST/websocket client used by gateway, resolver, and outbound |
3 | 3 | * delivery code. |
4 | 4 | */ |
| 5 | +import { readResponseTextLimited } from "openclaw/plugin-sdk/provider-http"; |
5 | 6 | import { WebSocket } from "ws"; |
6 | 7 | import type { |
7 | 8 | ClickClackChannel, |
@@ -17,6 +18,8 @@ type ClientOptions = {
|
17 | 18 | fetch?: typeof fetch; |
18 | 19 | }; |
19 | 20 | |
| 21 | +const CLICKCLACK_ERROR_BODY_LIMIT_BYTES = 8 * 1024; |
| 22 | + |
20 | 23 | /** |
21 | 24 | * Creates a typed client for the ClickClack API using bearer-token auth. |
22 | 25 | */ |
@@ -38,7 +41,8 @@ export function createClickClackClient(options: ClientOptions) {
|
38 | 41 | } |
39 | 42 | const response = await fetcher(`${baseUrl}${path}`, { ...init, headers: requestHeaders }); |
40 | 43 | if (!response.ok) { |
41 | | -throw new Error(`ClickClack ${response.status}: ${await response.text()}`); |
| 44 | +const detail = await readResponseTextLimited(response, CLICKCLACK_ERROR_BODY_LIMIT_BYTES); |
| 45 | +throw new Error(`ClickClack ${response.status}: ${detail}`); |
42 | 46 | } |
43 | 47 | return (await response.json()) as T; |
44 | 48 | } |
|
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。