mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-09 15:59:30 +00:00
fix(e2e): keep cross-os response reads timed
This commit is contained in:
parent
ea0b0ad0a0
commit
a619518ebe
2 changed files with 80 additions and 17 deletions
|
|
@ -2554,6 +2554,7 @@ async function configureDiscordSmoke(params) {
|
|||
export async function readBoundedCrossOsResponseText(
|
||||
response: Response,
|
||||
maxChars = CROSS_OS_FETCH_BODY_MAX_CHARS,
|
||||
options: { signal?: AbortSignal | null } = {},
|
||||
): Promise<string> {
|
||||
if (!response.body) {
|
||||
return "";
|
||||
|
|
@ -2563,10 +2564,11 @@ export async function readBoundedCrossOsResponseText(
|
|||
const decoder = new TextDecoder();
|
||||
let text = "";
|
||||
let truncated = false;
|
||||
let aborted = false;
|
||||
|
||||
try {
|
||||
while (text.length <= maxChars) {
|
||||
const { done, value } = await reader.read();
|
||||
const { done, value } = await readCrossOsResponseChunk(reader, options.signal);
|
||||
if (done) {
|
||||
text += decoder.decode();
|
||||
break;
|
||||
|
|
@ -2579,8 +2581,11 @@ export async function readBoundedCrossOsResponseText(
|
|||
break;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
aborted = options.signal?.aborted === true;
|
||||
throw error;
|
||||
} finally {
|
||||
if (truncated) {
|
||||
if (truncated || aborted) {
|
||||
await reader.cancel().catch(() => undefined);
|
||||
} else {
|
||||
reader.releaseLock();
|
||||
|
|
@ -2590,6 +2595,36 @@ export async function readBoundedCrossOsResponseText(
|
|||
return truncated ? `${text}\n[truncated]` : text;
|
||||
}
|
||||
|
||||
function readCrossOsResponseChunk(
|
||||
reader: ReadableStreamDefaultReader<Uint8Array>,
|
||||
signal?: AbortSignal | null,
|
||||
): Promise<ReadableStreamReadResult<Uint8Array>> {
|
||||
if (!signal) {
|
||||
return reader.read();
|
||||
}
|
||||
if (signal.aborted) {
|
||||
throw crossOsAbortReason(signal);
|
||||
}
|
||||
return new Promise((resolveRead, rejectRead) => {
|
||||
const onAbort = () => rejectRead(crossOsAbortReason(signal));
|
||||
signal.addEventListener("abort", onAbort, { once: true });
|
||||
reader
|
||||
.read()
|
||||
.then(resolveRead, rejectRead)
|
||||
.finally(() => {
|
||||
signal.removeEventListener("abort", onAbort);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function crossOsAbortReason(signal: AbortSignal): Error {
|
||||
const reason = signal.reason;
|
||||
if (reason instanceof Error) {
|
||||
return reason;
|
||||
}
|
||||
return new Error(typeof reason === "string" ? reason : "The operation was aborted.");
|
||||
}
|
||||
|
||||
export function dashboardHtmlMarkerStatus(html: string): {
|
||||
app: boolean;
|
||||
ready: boolean;
|
||||
|
|
@ -2647,11 +2682,12 @@ async function waitForDiscordMessage(params) {
|
|||
let response;
|
||||
let text;
|
||||
try {
|
||||
const init = buildDiscordFetchInit(params.token);
|
||||
response = await fetch(
|
||||
`https://discord.com/api/v10/channels/${params.channelId}/messages?limit=20`,
|
||||
buildDiscordFetchInit(params.token),
|
||||
init,
|
||||
);
|
||||
text = await readBoundedCrossOsResponseText(response);
|
||||
text = await readBoundedCrossOsResponseText(response, undefined, { signal: init.signal });
|
||||
} catch {
|
||||
await sleep(2_000);
|
||||
continue;
|
||||
|
|
@ -2680,20 +2716,21 @@ export function buildDiscordFetchInit(token, init = {}) {
|
|||
}
|
||||
|
||||
async function postDiscordMessage(params) {
|
||||
const init = buildDiscordFetchInit(params.token, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
content: params.content,
|
||||
flags: 4096,
|
||||
}),
|
||||
});
|
||||
const response = await fetch(
|
||||
`https://discord.com/api/v10/channels/${params.channelId}/messages`,
|
||||
buildDiscordFetchInit(params.token, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
content: params.content,
|
||||
flags: 4096,
|
||||
}),
|
||||
}),
|
||||
init,
|
||||
);
|
||||
const text = await readBoundedCrossOsResponseText(response);
|
||||
const text = await readBoundedCrossOsResponseText(response, undefined, { signal: init.signal });
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to post Discord smoke message: ${text}`);
|
||||
}
|
||||
|
|
@ -3525,10 +3562,11 @@ async function runDashboardSmoke(params) {
|
|||
attempt += 1;
|
||||
logStream.write(`${new Date().toISOString()} attempt=${attempt} url=${dashboardUrl}\n`);
|
||||
try {
|
||||
const signal = AbortSignal.timeout(CROSS_OS_DASHBOARD_FETCH_TIMEOUT_MS);
|
||||
const response = await fetch(dashboardUrl, {
|
||||
signal: AbortSignal.timeout(CROSS_OS_DASHBOARD_FETCH_TIMEOUT_MS),
|
||||
signal,
|
||||
});
|
||||
const html = await readBoundedCrossOsResponseText(response);
|
||||
const html = await readBoundedCrossOsResponseText(response, undefined, { signal });
|
||||
const markers = dashboardHtmlMarkerStatus(html);
|
||||
const assetUrls = resolveDashboardAssetUrls(dashboardUrl, html);
|
||||
if (response.ok && markers.ready) {
|
||||
|
|
|
|||
|
|
@ -166,6 +166,31 @@ describe("scripts/openclaw-cross-os-release-checks", () => {
|
|||
expect(CROSS_OS_FETCH_BODY_MAX_CHARS).toBeGreaterThan(1024);
|
||||
});
|
||||
|
||||
it("keeps cross-OS fetch timeouts active while reading response bodies", async () => {
|
||||
let canceled = false;
|
||||
const abortController = new AbortController();
|
||||
const response = new Response(
|
||||
new ReadableStream<Uint8Array>({
|
||||
start(controller) {
|
||||
controller.enqueue(new TextEncoder().encode("partial"));
|
||||
},
|
||||
cancel() {
|
||||
canceled = true;
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const text = readBoundedCrossOsResponseText(response, 1024, {
|
||||
signal: abortController.signal,
|
||||
});
|
||||
|
||||
await delay(0);
|
||||
abortController.abort(new Error("cross-os body timed out"));
|
||||
|
||||
await expect(text).rejects.toThrow("cross-os body timed out");
|
||||
expect(canceled).toBe(true);
|
||||
});
|
||||
|
||||
it("requires dashboard root markers and same-origin asset URLs", () => {
|
||||
const html = [
|
||||
"<title>OpenClaw Control</title>",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue