mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-11 09:11:13 +00:00
fix(fetch): honor mocked global fetch with dispatchers
This commit is contained in:
@@ -233,9 +233,11 @@ describe("fetchWithSsrFGuard hardening", () => {
|
||||
it("uses runtime undici fetch when attaching a dispatcher", async () => {
|
||||
const runtimeFetch = vi.fn(async () => okResponse());
|
||||
const originalGlobalFetch = globalThis.fetch;
|
||||
const globalFetch = vi.fn(async () => {
|
||||
let globalFetchCalls = 0;
|
||||
const globalFetch = async () => {
|
||||
globalFetchCalls += 1;
|
||||
throw new Error("global fetch should not be used when a dispatcher is attached");
|
||||
});
|
||||
};
|
||||
|
||||
class MockAgent {
|
||||
constructor(readonly options: unknown) {}
|
||||
@@ -262,7 +264,46 @@ describe("fetchWithSsrFGuard hardening", () => {
|
||||
});
|
||||
|
||||
expect(runtimeFetch).toHaveBeenCalledTimes(1);
|
||||
expect(globalFetch).not.toHaveBeenCalled();
|
||||
expect(globalFetchCalls).toBe(0);
|
||||
await result.release();
|
||||
} finally {
|
||||
(globalThis as Record<string, unknown>).fetch = originalGlobalFetch;
|
||||
}
|
||||
});
|
||||
|
||||
it("uses mocked global fetch when tests stub it", async () => {
|
||||
const runtimeFetch = vi.fn(async () => {
|
||||
throw new Error("runtime fetch should not be used when global fetch is mocked");
|
||||
});
|
||||
const originalGlobalFetch = globalThis.fetch;
|
||||
const globalFetch = vi.fn(async () => okResponse());
|
||||
|
||||
class MockAgent {
|
||||
constructor(readonly options: unknown) {}
|
||||
}
|
||||
class MockEnvHttpProxyAgent {
|
||||
constructor(readonly options: unknown) {}
|
||||
}
|
||||
class MockProxyAgent {
|
||||
constructor(readonly options: unknown) {}
|
||||
}
|
||||
|
||||
(globalThis as Record<string, unknown>).fetch = globalFetch as typeof fetch;
|
||||
(globalThis as Record<string, unknown>)[TEST_UNDICI_RUNTIME_DEPS_KEY] = {
|
||||
Agent: MockAgent,
|
||||
EnvHttpProxyAgent: MockEnvHttpProxyAgent,
|
||||
ProxyAgent: MockProxyAgent,
|
||||
fetch: runtimeFetch,
|
||||
};
|
||||
|
||||
try {
|
||||
const result = await fetchWithSsrFGuard({
|
||||
url: "https://public.example/resource",
|
||||
lookupFn: createPublicLookup(),
|
||||
});
|
||||
|
||||
expect(globalFetch).toHaveBeenCalledTimes(1);
|
||||
expect(runtimeFetch).not.toHaveBeenCalled();
|
||||
await result.release();
|
||||
} finally {
|
||||
(globalThis as Record<string, unknown>).fetch = originalGlobalFetch;
|
||||
|
||||
@@ -158,6 +158,13 @@ function isRedirectStatus(status: number): boolean {
|
||||
return status === 301 || status === 302 || status === 303 || status === 307 || status === 308;
|
||||
}
|
||||
|
||||
function isMockedFetch(fetchImpl: FetchLike | undefined): boolean {
|
||||
if (typeof fetchImpl !== "function") {
|
||||
return false;
|
||||
}
|
||||
return typeof (fetchImpl as FetchLike & { mock?: unknown }).mock === "object";
|
||||
}
|
||||
|
||||
export function retainSafeHeadersForCrossOriginRedirectHeaders(
|
||||
headers?: HeadersInit,
|
||||
): Record<string, string> | undefined {
|
||||
@@ -296,14 +303,17 @@ export async function fetchWithSsrFGuard(params: GuardedFetchOptions): Promise<G
|
||||
|
||||
const supportsDispatcherInit =
|
||||
params.fetchImpl !== undefined ||
|
||||
isMockedFetch(defaultFetch) ||
|
||||
(defaultFetch as DispatcherCompatibleFetch).__openclawAcceptsDispatcher === true;
|
||||
// Use caller-provided fetch stubs when present; otherwise fall back to
|
||||
// undici's fetch whenever we attach a dispatcher because the global fetch
|
||||
// path will not honor per-request dispatchers.
|
||||
const response =
|
||||
dispatcher && !supportsDispatcherInit
|
||||
? await fetchWithRuntimeDispatcher(parsedUrl.toString(), init)
|
||||
: await defaultFetch(parsedUrl.toString(), init);
|
||||
// Explicit caller stubs, test-installed global fetch mocks, and
|
||||
// dispatcher-aware wrappers should win.
|
||||
// Otherwise, fall back to undici's fetch whenever we attach a dispatcher,
|
||||
// because the default global fetch path will not honor per-request
|
||||
// dispatchers.
|
||||
const shouldUseRuntimeFetch = Boolean(dispatcher) && !supportsDispatcherInit;
|
||||
const response = shouldUseRuntimeFetch
|
||||
? await fetchWithRuntimeDispatcher(parsedUrl.toString(), init)
|
||||
: await defaultFetch(parsedUrl.toString(), init);
|
||||
|
||||
if (isRedirectStatus(response.status)) {
|
||||
const location = response.headers.get("location");
|
||||
|
||||
Reference in New Issue
Block a user