fix(openai): cancel OAuth preflight bodies

This commit is contained in:
Vincent Koc 2026-06-19 09:53:30 +02:00
parent ca527aad9d
commit a6ac8de523
No known key found for this signature in database
4 changed files with 32 additions and 6 deletions

View file

@ -22,4 +22,19 @@ describe("OpenAI Codex OAuth runtime", () => {
expect(timeoutSpy).toHaveBeenCalledWith(MAX_TIMER_TIMEOUT_MS);
expect(fetchImpl).toHaveBeenCalledTimes(1);
});
it("cancels reachable TLS preflight response bodies", async () => {
const response = new Response("reachable", { status: 302 });
const cancel = vi.spyOn(response.body!, "cancel").mockResolvedValue(undefined);
const fetchImpl = vi.fn(async () => response);
await expect(
testing.runOpenAIOAuthTlsPreflight({
timeoutMs: 20,
fetchImpl,
}),
).resolves.toEqual({ ok: true });
expect(cancel).toHaveBeenCalledOnce();
});
});

View file

@ -94,8 +94,9 @@ async function runOpenAIOAuthTlsPreflight(options?: {
}): Promise<OpenAIOAuthTlsPreflightResult> {
const timeoutMs = resolveTimerTimeoutMs(options?.timeoutMs, 5000);
const fetchImpl = options?.fetchImpl ?? fetch;
let response: Response | undefined;
try {
await fetchImpl(openAIAuthProbeUrl, {
response = await fetchImpl(openAIAuthProbeUrl, {
method: "GET",
redirect: "manual",
signal: AbortSignal.timeout(timeoutMs),
@ -109,6 +110,10 @@ async function runOpenAIOAuthTlsPreflight(options?: {
code: failure.code,
message: failure.message,
};
} finally {
if (response?.bodyUsed !== true) {
await response?.body?.cancel().catch(() => undefined);
}
}
}

View file

@ -1,12 +1,12 @@
// OAuth TLS preflight tests cover timeout handling, TLS diagnostics, and suggested fixes.
import { MAX_TIMER_TIMEOUT_MS } from "@openclaw/normalization-core/number-coercion";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { withEnv } from "../test-utils/env.js";
import {
formatOpenAIOAuthTlsPreflightFix,
runOpenAIOAuthTlsPreflight,
shouldRunOpenAIOAuthTlsPrerequisites,
} from "../plugins/provider-openai-chatgpt-oauth-tls.js";
import { withEnv } from "../test-utils/env.js";
describe("runOpenAIOAuthTlsPreflight", () => {
beforeEach(() => {
@ -14,11 +14,12 @@ describe("runOpenAIOAuthTlsPreflight", () => {
});
it("returns ok when OpenAI auth endpoint is reachable", async () => {
const fetchImpl = vi.fn(
async () => new Response("", { status: 400 }),
) as unknown as typeof fetch;
const response = new Response("reachable", { status: 400 });
const cancel = vi.spyOn(response.body!, "cancel").mockResolvedValue(undefined);
const fetchImpl = vi.fn(async () => response) as unknown as typeof fetch;
const result = await runOpenAIOAuthTlsPreflight({ fetchImpl, timeoutMs: 20 });
expect(result).toEqual({ ok: true });
expect(cancel).toHaveBeenCalledOnce();
});
it("caps oversized probe timeouts before creating abort signals", async () => {

View file

@ -105,8 +105,9 @@ export async function runOpenAIOAuthTlsPreflight(options?: {
}): Promise<OpenAIOAuthTlsPreflightResult> {
const timeoutMs = resolveTimerTimeoutMs(options?.timeoutMs, 5000);
const fetchImpl = options?.fetchImpl ?? fetch;
let response: Response | undefined;
try {
await fetchImpl(OPENAI_AUTH_PROBE_URL, {
response = await fetchImpl(OPENAI_AUTH_PROBE_URL, {
method: "GET",
redirect: "manual",
signal: AbortSignal.timeout(timeoutMs),
@ -120,6 +121,10 @@ export async function runOpenAIOAuthTlsPreflight(options?: {
code: failure.code,
message: failure.message,
};
} finally {
if (response?.bodyUsed !== true) {
await response?.body?.cancel().catch(() => undefined);
}
}
}